From 0426b211f62a618f7277b9f178e832309699fbc5 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 7 Jan 2021 12:56:52 -0600 Subject: [PATCH] Rewrite Plex tests to use mocked payloads (#44044) --- .coveragerc | 2 - homeassistant/components/plex/server.py | 2 +- tests/components/plex/conftest.py | 371 +++++++++++- tests/components/plex/const.py | 2 + tests/components/plex/mock_classes.py | 530 ------------------ tests/components/plex/test_browse_media.py | 17 +- tests/components/plex/test_config_flow.py | 227 ++++---- tests/components/plex/test_init.py | 101 ++-- tests/components/plex/test_media_players.py | 34 +- tests/components/plex/test_playback.py | 92 ++- tests/components/plex/test_server.py | 328 +++++------ tests/components/plex/test_services.py | 111 ++-- tests/fixtures/plex/album.xml | 4 + tests/fixtures/plex/artist_albums.xml | 4 + tests/fixtures/plex/children_20.xml | 11 + tests/fixtures/plex/children_200.xml | 1 + tests/fixtures/plex/children_30.xml | 4 + tests/fixtures/plex/children_300.xml | 4 + tests/fixtures/plex/empty_library.xml | 1 + tests/fixtures/plex/empty_payload.xml | 1 + tests/fixtures/plex/grandchildren_300.xml | 1 + tests/fixtures/plex/library.xml | 5 + tests/fixtures/plex/library_movies_all.xml | 51 ++ tests/fixtures/plex/library_movies_sort.xml | 10 + tests/fixtures/plex/library_music_all.xml | 6 + tests/fixtures/plex/library_music_sort.xml | 7 + tests/fixtures/plex/library_sections.xml | 11 + tests/fixtures/plex/library_tvshows_all.xml | 6 + tests/fixtures/plex/library_tvshows_sort.xml | 8 + tests/fixtures/plex/media_1.xml | 11 + tests/fixtures/plex/media_100.xml | 1 + tests/fixtures/plex/media_200.xml | 4 + tests/fixtures/plex/media_30.xml | 6 + .../plex/player_plexweb_resources.xml | 1 + tests/fixtures/plex/playlist_500.xml | 11 + tests/fixtures/plex/playlists.xml | 6 + tests/fixtures/plex/playqueue_created.xml | 1 + tests/fixtures/plex/plex_server_accounts.xml | 6 + tests/fixtures/plex/plex_server_base.xml | 27 + tests/fixtures/plex/plex_server_clients.xml | 3 + tests/fixtures/plex/plextv_account.xml | 15 + tests/fixtures/plex/plextv_resources_base.xml | 21 + tests/fixtures/plex/security_token.xml | 1 + tests/fixtures/plex/session_base.xml | 11 + tests/fixtures/plex/session_photo.xml | 5 + tests/fixtures/plex/session_plexweb.xml | 11 + tests/fixtures/plex/show_seasons.xml | 4 + tests/fixtures/plex/sonos_resources.xml | 5 + 48 files changed, 1113 insertions(+), 989 deletions(-) create mode 100644 tests/fixtures/plex/album.xml create mode 100644 tests/fixtures/plex/artist_albums.xml create mode 100644 tests/fixtures/plex/children_20.xml create mode 100644 tests/fixtures/plex/children_200.xml create mode 100644 tests/fixtures/plex/children_30.xml create mode 100644 tests/fixtures/plex/children_300.xml create mode 100644 tests/fixtures/plex/empty_library.xml create mode 100644 tests/fixtures/plex/empty_payload.xml create mode 100644 tests/fixtures/plex/grandchildren_300.xml create mode 100644 tests/fixtures/plex/library.xml create mode 100644 tests/fixtures/plex/library_movies_all.xml create mode 100644 tests/fixtures/plex/library_movies_sort.xml create mode 100644 tests/fixtures/plex/library_music_all.xml create mode 100644 tests/fixtures/plex/library_music_sort.xml create mode 100644 tests/fixtures/plex/library_sections.xml create mode 100644 tests/fixtures/plex/library_tvshows_all.xml create mode 100644 tests/fixtures/plex/library_tvshows_sort.xml create mode 100644 tests/fixtures/plex/media_1.xml create mode 100644 tests/fixtures/plex/media_100.xml create mode 100644 tests/fixtures/plex/media_200.xml create mode 100644 tests/fixtures/plex/media_30.xml create mode 100644 tests/fixtures/plex/player_plexweb_resources.xml create mode 100644 tests/fixtures/plex/playlist_500.xml create mode 100644 tests/fixtures/plex/playlists.xml create mode 100644 tests/fixtures/plex/playqueue_created.xml create mode 100644 tests/fixtures/plex/plex_server_accounts.xml create mode 100644 tests/fixtures/plex/plex_server_base.xml create mode 100644 tests/fixtures/plex/plex_server_clients.xml create mode 100644 tests/fixtures/plex/plextv_account.xml create mode 100644 tests/fixtures/plex/plextv_resources_base.xml create mode 100644 tests/fixtures/plex/security_token.xml create mode 100644 tests/fixtures/plex/session_base.xml create mode 100644 tests/fixtures/plex/session_photo.xml create mode 100644 tests/fixtures/plex/session_plexweb.xml create mode 100644 tests/fixtures/plex/show_seasons.xml create mode 100644 tests/fixtures/plex/sonos_resources.xml diff --git a/.coveragerc b/.coveragerc index b859e229e74..b3e58591bfa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -694,8 +694,6 @@ omit = homeassistant/components/pjlink/media_player.py homeassistant/components/plaato/* homeassistant/components/plex/media_player.py - homeassistant/components/plex/models.py - homeassistant/components/plex/sensor.py homeassistant/components/plum_lightpad/light.py homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 3834833b740..f8d55c71fc4 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -146,7 +146,7 @@ class PlexServer: available_servers = [ (x.name, x.clientIdentifier) for x in self.account.resources() - if "server" in x.provides + if "server" in x.provides and x.presence ] if not available_servers: diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 50fcf3eb64d..8fc25a819e8 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -3,13 +3,261 @@ from unittest.mock import patch import pytest -from homeassistant.components.plex.const import DOMAIN +from homeassistant.components.plex.const import DOMAIN, PLEX_SERVER_CONFIG, SERVERS +from homeassistant.const import CONF_URL -from .const import DEFAULT_DATA, DEFAULT_OPTIONS +from .const import DEFAULT_DATA, DEFAULT_OPTIONS, PLEX_DIRECT_URL from .helpers import websocket_connected -from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer +from .mock_classes import MockGDM -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture + + +def plex_server_url(entry): + """Return a protocol-less URL from a config entry.""" + return entry.data[PLEX_SERVER_CONFIG][CONF_URL].split(":", 1)[-1] + + +@pytest.fixture(name="album", scope="session") +def album_fixture(): + """Load album payload and return it.""" + return load_fixture("plex/album.xml") + + +@pytest.fixture(name="artist_albums", scope="session") +def artist_albums_fixture(): + """Load artist's albums payload and return it.""" + return load_fixture("plex/artist_albums.xml") + + +@pytest.fixture(name="children_20", scope="session") +def children_20_fixture(): + """Load children payload for item 20 and return it.""" + return load_fixture("plex/children_20.xml") + + +@pytest.fixture(name="children_30", scope="session") +def children_30_fixture(): + """Load children payload for item 30 and return it.""" + return load_fixture("plex/children_30.xml") + + +@pytest.fixture(name="children_200", scope="session") +def children_200_fixture(): + """Load children payload for item 200 and return it.""" + return load_fixture("plex/children_200.xml") + + +@pytest.fixture(name="children_300", scope="session") +def children_300_fixture(): + """Load children payload for item 300 and return it.""" + return load_fixture("plex/children_300.xml") + + +@pytest.fixture(name="empty_library", scope="session") +def empty_library_fixture(): + """Load an empty library payload and return it.""" + return load_fixture("plex/empty_library.xml") + + +@pytest.fixture(name="empty_payload", scope="session") +def empty_payload_fixture(): + """Load an empty payload and return it.""" + return load_fixture("plex/empty_payload.xml") + + +@pytest.fixture(name="grandchildren_300", scope="session") +def grandchildren_300_fixture(): + """Load grandchildren payload for item 300 and return it.""" + return load_fixture("plex/grandchildren_300.xml") + + +@pytest.fixture(name="library_movies_all", scope="session") +def library_movies_all_fixture(): + """Load payload for all items in the movies library and return it.""" + return load_fixture("plex/library_movies_all.xml") + + +@pytest.fixture(name="library_tvshows_all", scope="session") +def library_tvshows_all_fixture(): + """Load payload for all items in the tvshows library and return it.""" + return load_fixture("plex/library_tvshows_all.xml") + + +@pytest.fixture(name="library_music_all", scope="session") +def library_music_all_fixture(): + """Load payload for all items in the music library and return it.""" + return load_fixture("plex/library_music_all.xml") + + +@pytest.fixture(name="library_movies_sort", scope="session") +def library_movies_sort_fixture(): + """Load sorting payload for movie library and return it.""" + return load_fixture("plex/library_movies_sort.xml") + + +@pytest.fixture(name="library_tvshows_sort", scope="session") +def library_tvshows_sort_fixture(): + """Load sorting payload for tvshow library and return it.""" + return load_fixture("plex/library_tvshows_sort.xml") + + +@pytest.fixture(name="library_music_sort", scope="session") +def library_music_sort_fixture(): + """Load sorting payload for music library and return it.""" + return load_fixture("plex/library_music_sort.xml") + + +@pytest.fixture(name="library", scope="session") +def library_fixture(): + """Load library payload and return it.""" + return load_fixture("plex/library.xml") + + +@pytest.fixture(name="library_sections", scope="session") +def library_sections_fixture(): + """Load library sections payload and return it.""" + return load_fixture("plex/library_sections.xml") + + +@pytest.fixture(name="media_1", scope="session") +def media_1_fixture(): + """Load media payload for item 1 and return it.""" + return load_fixture("plex/media_1.xml") + + +@pytest.fixture(name="media_30", scope="session") +def media_30_fixture(): + """Load media payload for item 30 and return it.""" + return load_fixture("plex/media_30.xml") + + +@pytest.fixture(name="media_100", scope="session") +def media_100_fixture(): + """Load media payload for item 100 and return it.""" + return load_fixture("plex/media_100.xml") + + +@pytest.fixture(name="media_200", scope="session") +def media_200_fixture(): + """Load media payload for item 200 and return it.""" + return load_fixture("plex/media_200.xml") + + +@pytest.fixture(name="player_plexweb_resources", scope="session") +def player_plexweb_resources_fixture(): + """Load resources payload for a Plex Web player and return it.""" + return load_fixture("plex/player_plexweb_resources.xml") + + +@pytest.fixture(name="playlists", scope="session") +def playlists_fixture(): + """Load payload for all playlists and return it.""" + return load_fixture("plex/playlists.xml") + + +@pytest.fixture(name="playlist_500", scope="session") +def playlist_500_fixture(): + """Load payload for playlist 500 and return it.""" + return load_fixture("plex/playlist_500.xml") + + +@pytest.fixture(name="playqueue_created", scope="session") +def playqueue_created_fixture(): + """Load payload for playqueue creation response and return it.""" + return load_fixture("plex/playqueue_created.xml") + + +@pytest.fixture(name="plex_server_accounts", scope="session") +def plex_server_accounts_fixture(): + """Load payload accounts on the Plex server and return it.""" + return load_fixture("plex/plex_server_accounts.xml") + + +@pytest.fixture(name="plex_server_base", scope="session") +def plex_server_base_fixture(): + """Load base payload for Plex server info and return it.""" + return load_fixture("plex/plex_server_base.xml") + + +@pytest.fixture(name="plex_server_default", scope="session") +def plex_server_default_fixture(plex_server_base): + """Load default payload for Plex server info and return it.""" + return plex_server_base.format( + name="Plex Server 1", machine_identifier="unique_id_123" + ) + + +@pytest.fixture(name="plex_server_clients", scope="session") +def plex_server_clients_fixture(): + """Load available clients payload for Plex server and return it.""" + return load_fixture("plex/plex_server_clients.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="plextv_resources_base", scope="session") +def plextv_resources_base_fixture(): + """Load base payload for plex.tv resources and return it.""" + return load_fixture("plex/plextv_resources_base.xml") + + +@pytest.fixture(name="plextv_resources", scope="session") +def plextv_resources_fixture(plextv_resources_base): + """Load default payload for plex.tv resources and return it.""" + return plextv_resources_base.format(second_server_enabled=0) + + +@pytest.fixture(name="session_base", scope="session") +def session_base_fixture(): + """Load the base session payload and return it.""" + return load_fixture("plex/session_base.xml") + + +@pytest.fixture(name="session_default", scope="session") +def session_default_fixture(session_base): + """Load the default session payload and return it.""" + return session_base.format(user_id=1) + + +@pytest.fixture(name="session_new_user", scope="session") +def session_new_user_fixture(session_base): + """Load the new user session payload and return it.""" + return session_base.format(user_id=1001) + + +@pytest.fixture(name="session_photo", scope="session") +def session_photo_fixture(): + """Load a photo session payload and return it.""" + return load_fixture("plex/session_photo.xml") + + +@pytest.fixture(name="session_plexweb", scope="session") +def session_plexweb_fixture(): + """Load a Plex Web session payload and return it.""" + return load_fixture("plex/session_plexweb.xml") + + +@pytest.fixture(name="security_token", scope="session") +def security_token_fixture(): + """Load a security token payload and return it.""" + return load_fixture("plex/security_token.xml") + + +@pytest.fixture(name="show_seasons", scope="session") +def show_seasons_fixture(): + """Load a show's seasons payload and return it.""" + 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") @@ -23,14 +271,6 @@ def mock_config_entry(): ) -@pytest.fixture -def mock_plex_account(): - """Mock the PlexAccount class and return the used instance.""" - plex_account = MockPlexAccount() - with patch("plexapi.myplex.MyPlexAccount", return_value=plex_account): - yield plex_account - - @pytest.fixture def mock_websocket(): """Mock the PlexWebsocket class.""" @@ -39,15 +279,112 @@ def mock_websocket(): @pytest.fixture -def setup_plex_server(hass, entry, mock_plex_account, mock_websocket): +def mock_plex_calls( + entry, + requests_mock, + children_20, + children_30, + children_200, + children_300, + empty_library, + grandchildren_300, + library, + library_sections, + library_movies_all, + library_movies_sort, + library_music_all, + library_music_sort, + library_tvshows_all, + library_tvshows_sort, + media_1, + media_30, + media_100, + media_200, + playlists, + playlist_500, + plextv_account, + plextv_resources, + plex_server_accounts, + plex_server_clients, + plex_server_default, + security_token, +): + """Mock Plex API calls.""" + requests_mock.get("https://plex.tv/users/account", text=plextv_account) + requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) + + url = plex_server_url(entry) + + for server in [url, PLEX_DIRECT_URL]: + requests_mock.get(server, text=plex_server_default) + requests_mock.get(f"{server}/accounts", text=plex_server_accounts) + + requests_mock.get(f"{url}/clients", text=plex_server_clients) + requests_mock.get(f"{url}/library", text=library) + requests_mock.get(f"{url}/library/sections", text=library_sections) + + requests_mock.get(f"{url}/library/onDeck", text=empty_library) + requests_mock.get(f"{url}/library/sections/1/sorts", text=library_movies_sort) + requests_mock.get(f"{url}/library/sections/2/sorts", text=library_tvshows_sort) + requests_mock.get(f"{url}/library/sections/3/sorts", text=library_music_sort) + + requests_mock.get(f"{url}/library/sections/1/all", text=library_movies_all) + requests_mock.get(f"{url}/library/sections/2/all", text=library_tvshows_all) + requests_mock.get(f"{url}/library/sections/3/all", text=library_music_all) + + requests_mock.get(f"{url}/library/metadata/200/children", text=children_200) + requests_mock.get(f"{url}/library/metadata/300/children", text=children_300) + requests_mock.get(f"{url}/library/metadata/300/allLeaves", text=grandchildren_300) + + requests_mock.get(f"{url}/library/metadata/1", text=media_1) + requests_mock.get(f"{url}/library/metadata/30", text=media_30) + requests_mock.get(f"{url}/library/metadata/100", text=media_100) + requests_mock.get(f"{url}/library/metadata/200", text=media_200) + + requests_mock.get(f"{url}/library/metadata/20/children", text=children_20) + requests_mock.get(f"{url}/library/metadata/30/children", text=children_30) + + requests_mock.get(f"{url}/playlists", text=playlists) + requests_mock.get(f"{url}/playlists/500/items", text=playlist_500) + requests_mock.get(f"{url}/security/token", text=security_token) + + +@pytest.fixture +def setup_plex_server( + hass, + entry, + mock_websocket, + mock_plex_calls, + requests_mock, + empty_payload, + session_default, + session_photo, + session_plexweb, +): """Set up and return a mocked Plex server instance.""" async def _wrapper(**kwargs): - """Wrap the fixture to allow passing arguments to the MockPlexServer instance.""" + """Wrap the fixture to allow passing arguments to the setup method.""" config_entry = kwargs.get("config_entry", entry) + disable_clients = kwargs.pop("disable_clients", False) disable_gdm = kwargs.pop("disable_gdm", True) - plex_server = MockPlexServer(**kwargs) - with patch("plexapi.server.PlexServer", return_value=plex_server), patch( + client_type = kwargs.pop("client_type", None) + session_type = kwargs.pop("session_type", None) + + if client_type == "plexweb": + session = session_plexweb + elif session_type == "photo": + session = session_photo + else: + session = session_default + + url = plex_server_url(entry) + requests_mock.get(f"{url}/status/sessions", text=session) + + if disable_clients: + requests_mock.get(f"{url}/clients", text=empty_payload) + + with patch( "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=disable_gdm), ): @@ -56,6 +393,8 @@ def setup_plex_server(hass, entry, mock_plex_account, mock_websocket): await hass.async_block_till_done() websocket_connected(mock_websocket) await hass.async_block_till_done() + + plex_server = hass.data[DOMAIN][SERVERS][entry.unique_id] return plex_server return _wrapper diff --git a/tests/components/plex/const.py b/tests/components/plex/const.py index 548be2edeb8..9e376d19cac 100644 --- a/tests/components/plex/const.py +++ b/tests/components/plex/const.py @@ -61,3 +61,5 @@ DEFAULT_OPTIONS = { const.CONF_USE_EPISODE_ART: False, } } + +PLEX_DIRECT_URL = "https://1-2-3-4.123456789001234567890.plex.direct:32400" diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 5f2fad6a8f1..c6f1aeda9b7 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -1,17 +1,4 @@ """Mock classes used in tests.""" -from functools import lru_cache - -from aiohttp.helpers import reify -from plexapi.exceptions import NotFound - -from homeassistant.components.plex.const import ( - CONF_SERVER, - CONF_SERVER_IDENTIFIER, - PLEX_SERVER_CONFIG, -) -from homeassistant.const import CONF_URL - -from .const import DEFAULT_DATA, MOCK_SERVERS, MOCK_USERS GDM_SERVER_PAYLOAD = [ { @@ -94,520 +81,3 @@ class MockGDM: self.entries = GDM_CLIENT_PAYLOAD else: self.entries = GDM_SERVER_PAYLOAD - - -class MockResource: - """Mock a PlexAccount resource.""" - - def __init__(self, index, kind="server"): - """Initialize the object.""" - if kind == "server": - self.name = MOCK_SERVERS[index][CONF_SERVER] - self.clientIdentifier = MOCK_SERVERS[index][ # pylint: disable=invalid-name - CONF_SERVER_IDENTIFIER - ] - self.provides = ["server"] - self.device = MockPlexServer(index) - else: - self.name = f"plex.tv Resource Player {index+10}" - self.clientIdentifier = f"client-{index+10}" - self.provides = ["player"] - self.device = MockPlexClient( - baseurl=f"http://192.168.0.1{index}:32500", index=index + 10 - ) - self.presence = index == 0 - self.publicAddressMatches = True - - def connect(self, timeout): - """Mock the resource connect method.""" - return self.device - - -class MockPlexAccount: - """Mock a PlexAccount instance.""" - - def __init__(self, servers=1, players=3): - """Initialize the object.""" - self._resources = [] - for index in range(servers): - self._resources.append(MockResource(index)) - for index in range(players): - self._resources.append(MockResource(index, kind="player")) - - def resource(self, name): - """Mock the PlexAccount resource lookup method.""" - return [x for x in self._resources if x.name == name][0] - - def resources(self): - """Mock the PlexAccount resources listing method.""" - return self._resources - - def sonos_speaker(self, speaker_name): - """Mock the PlexAccount Sonos lookup method.""" - return MockPlexSonosClient(speaker_name) - - -class MockPlexSystemAccount: - """Mock a PlexSystemAccount instance.""" - - def __init__(self, index): - """Initialize the object.""" - # Start accountIDs at 1 to set proper owner. - self.name = list(MOCK_USERS)[index] - self.accountID = index + 1 - - -class MockPlexServer: - """Mock a PlexServer instance.""" - - def __init__( - self, - index=0, - config_entry=None, - num_users=len(MOCK_USERS), - session_type="video", - ): - """Initialize the object.""" - if config_entry: - self._data = config_entry.data - else: - self._data = DEFAULT_DATA - - self._baseurl = self._data[PLEX_SERVER_CONFIG][CONF_URL] - self.friendlyName = self._data[CONF_SERVER] - self.machineIdentifier = self._data[CONF_SERVER_IDENTIFIER] - - self._systemAccounts = list(map(MockPlexSystemAccount, range(num_users))) - - self._clients = [] - self._session = None - self._sessions = [] - self.set_clients(num_users) - self.set_sessions(num_users, session_type) - - self._cache = {} - - def set_clients(self, num_clients): - """Set up mock PlexClients for this PlexServer.""" - self._clients = [ - MockPlexClient(baseurl=self._baseurl, index=x) for x in range(num_clients) - ] - - def set_sessions(self, num_sessions, session_type): - """Set up mock PlexSessions for this PlexServer.""" - self._sessions = [ - MockPlexSession(self._clients[x], mediatype=session_type, index=x) - for x in range(num_sessions) - ] - - def clear_clients(self): - """Clear all active PlexClients.""" - self._clients = [] - - def clear_sessions(self): - """Clear all active PlexSessions.""" - self._sessions = [] - - def clients(self): - """Mock the clients method.""" - return self._clients - - def createToken(self): - """Mock the createToken method.""" - return "temporary_token" - - def sessions(self): - """Mock the sessions method.""" - return self._sessions - - def systemAccounts(self): - """Mock the systemAccounts lookup method.""" - return self._systemAccounts - - def url(self, path, includeToken=False): - """Mock method to generate a server URL.""" - return f"{self._baseurl}{path}" - - @property - def accounts(self): - """Mock the accounts property.""" - return set(MOCK_USERS) - - @property - def version(self): - """Mock version of PlexServer.""" - return "1.0" - - @reify - def library(self): - """Mock library object of PlexServer.""" - return MockPlexLibrary(self) - - def playlist(self, playlist): - """Mock the playlist lookup method.""" - return MockPlexMediaItem(playlist, mediatype="playlist") - - @lru_cache - def playlists(self): - """Mock the playlists lookup method with a lazy init.""" - return [ - MockPlexPlaylist( - self.library.section("Movies").all() - + self.library.section("TV Shows").all() - ), - MockPlexPlaylist(self.library.section("Music").all()), - ] - - def fetchItem(self, item): - """Mock the fetchItem method.""" - for section in self.library.sections(): - result = section.fetchItem(item) - if result: - return result - - -class MockPlexClient: - """Mock a PlexClient instance.""" - - def __init__(self, server=None, baseurl=None, token=None, index=0): - """Initialize the object.""" - self.machineIdentifier = f"client-{index+1}" - self._baseurl = baseurl - self._index = index - - def url(self, key): - """Mock the url method.""" - return f"{self._baseurl}{key}" - - @property - def device(self): - """Mock the device attribute.""" - return "DEVICE" - - @property - def platform(self): - """Mock the platform attribute.""" - return "PLATFORM" - - @property - def product(self): - """Mock the product attribute.""" - if self._index == 1: - return "Plex Web" - return "PRODUCT" - - @property - def protocolCapabilities(self): - """Mock the protocolCapabilities attribute.""" - return ["playback"] - - @property - def state(self): - """Mock the state attribute.""" - return "playing" - - @property - def title(self): - """Mock the title attribute.""" - return "TITLE" - - @property - def version(self): - """Mock the version attribute.""" - return "1.0" - - def proxyThroughServer(self, value=True, server=None): - """Mock the proxyThroughServer method.""" - pass - - def playMedia(self, item): - """Mock the playMedia method.""" - pass - - -class MockPlexSession: - """Mock a PlexServer.sessions() instance.""" - - def __init__(self, player, mediatype, index=0): - """Initialize the object.""" - self.TYPE = mediatype - self.usernames = [list(MOCK_USERS)[index]] - self.players = [player] - self._section = MockPlexLibrarySection("Movies") - self.sessionKey = index + 1 - - @property - def duration(self): - """Mock the duration attribute.""" - return 10000000 - - @property - def librarySectionID(self): - """Mock the librarySectionID attribute.""" - return 1 - - @property - def ratingKey(self): - """Mock the ratingKey attribute.""" - return 123 - - def section(self): - """Mock the section method.""" - return self._section - - @property - def summary(self): - """Mock the summary attribute.""" - return "SUMMARY" - - @property - def thumbUrl(self): - """Mock the thumbUrl attribute.""" - return "http://1.2.3.4/thumb" - - @property - def title(self): - """Mock the title attribute.""" - return "TITLE" - - @property - def type(self): - """Mock the type attribute.""" - return "movie" - - @property - def viewOffset(self): - """Mock the viewOffset attribute.""" - return 0 - - @property - def year(self): - """Mock the year attribute.""" - return 2020 - - -class MockPlexLibrary: - """Mock a Plex Library instance.""" - - def __init__(self, plex_server): - """Initialize the object.""" - self._plex_server = plex_server - self._sections = {} - - for kind in ["Movies", "Music", "TV Shows", "Photos"]: - self._sections[kind] = MockPlexLibrarySection(kind) - - def section(self, title): - """Mock the LibrarySection lookup.""" - section = self._sections.get(title) - if section: - return section - raise NotFound - - def sections(self): - """Return all available sections.""" - return self._sections.values() - - def sectionByID(self, section_id): - """Mock the sectionByID lookup.""" - return [x for x in self.sections() if x.key == section_id][0] - - def onDeck(self): - """Mock an empty On Deck folder.""" - return [] - - def recentlyAdded(self): - """Mock an empty Recently Added folder.""" - return [] - - -class MockPlexLibrarySection: - """Mock a Plex LibrarySection instance.""" - - def __init__(self, library): - """Initialize the object.""" - self.title = library - - if library == "Music": - self._item = MockPlexArtist("Artist") - elif library == "TV Shows": - self._item = MockPlexShow("TV Show") - else: - self._item = MockPlexMediaItem(library[:-1]) - - def get(self, query): - """Mock the get lookup method.""" - if self._item.title == query: - return self._item - raise NotFound - - def all(self): - """Mock the all method.""" - return [self._item] - - def fetchItem(self, ratingKey): - """Return a specific item.""" - for item in self.all(): - if item.ratingKey == ratingKey: - return item - if item._children: - for child in item._children: - if child.ratingKey == ratingKey: - return child - - def onDeck(self): - """Mock an empty On Deck folder.""" - return [] - - def recentlyAdded(self): - """Mock an empty Recently Added folder.""" - return self.all() - - @property - def type(self): - """Mock the library type.""" - if self.title == "Movies": - return "movie" - if self.title == "Music": - return "artist" - if self.title == "TV Shows": - return "show" - if self.title == "Photos": - return "photo" - - @property - def TYPE(self): - """Return the library type.""" - return self.type - - @property - def key(self): - """Mock the key identifier property.""" - return str(id(self.title)) - - def search(self, **kwargs): - """Mock the LibrarySection search method.""" - if kwargs.get("libtype") == "movie": - return self.all() - - def update(self): - """Mock the update call.""" - pass - - -class MockPlexMediaItem: - """Mock a Plex Media instance.""" - - def __init__(self, title, mediatype="video", year=2020): - """Initialize the object.""" - self.title = str(title) - self.type = mediatype - self.thumbUrl = "http://1.2.3.4/thumb.png" - self.year = year - self._children = [] - - def __iter__(self): - """Provide iterator.""" - yield from self._children - - @property - def ratingKey(self): - """Mock the ratingKey property.""" - return id(self.title) - - -class MockPlexPlaylist(MockPlexMediaItem): - """Mock a Plex Playlist instance.""" - - def __init__(self, items): - """Initialize the object.""" - super().__init__(f"Playlist ({len(items)} Items)", "playlist") - for item in items: - self._children.append(item) - - -class MockPlexShow(MockPlexMediaItem): - """Mock a Plex Show instance.""" - - def __init__(self, show): - """Initialize the object.""" - super().__init__(show, "show") - for index in range(1, 5): - self._children.append(MockPlexSeason(index)) - - def season(self, season): - """Mock the season lookup method.""" - return [x for x in self._children if x.title == f"Season {season}"][0] - - -class MockPlexSeason(MockPlexMediaItem): - """Mock a Plex Season instance.""" - - def __init__(self, season): - """Initialize the object.""" - super().__init__(f"Season {season}", "season") - for index in range(1, 10): - self._children.append(MockPlexMediaItem(f"Episode {index}", "episode")) - - def episode(self, episode): - """Mock the episode lookup method.""" - return self._children[episode - 1] - - -class MockPlexAlbum(MockPlexMediaItem): - """Mock a Plex Album instance.""" - - def __init__(self, album): - """Initialize the object.""" - super().__init__(album, "album") - for index in range(1, 10): - self._children.append(MockPlexMediaTrack(index)) - - def track(self, track): - """Mock the track lookup method.""" - try: - return [x for x in self._children if x.title == track][0] - except IndexError: - raise NotFound - - def tracks(self): - """Mock the tracks lookup method.""" - return self._children - - -class MockPlexArtist(MockPlexMediaItem): - """Mock a Plex Artist instance.""" - - def __init__(self, artist): - """Initialize the object.""" - super().__init__(artist, "artist") - self._album = MockPlexAlbum("Album") - - def album(self, album): - """Mock the album lookup method.""" - return self._album - - def get(self, track): - """Mock the track lookup method.""" - return self._album.track(track) - - -class MockPlexMediaTrack(MockPlexMediaItem): - """Mock a Plex Track instance.""" - - def __init__(self, index=1): - """Initialize the object.""" - super().__init__(f"Track {index}", "track") - self.index = index - - -class MockPlexSonosClient: - """Mock a PlexSonosClient instance.""" - - def __init__(self, name): - """Initialize the object.""" - self.name = name - - def playMedia(self, item): - """Mock the playMedia method.""" - pass diff --git a/tests/components/plex/test_browse_media.py b/tests/components/plex/test_browse_media.py index 66cbc51ef82..f9966a18c27 100644 --- a/tests/components/plex/test_browse_media.py +++ b/tests/components/plex/test_browse_media.py @@ -10,7 +10,7 @@ from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE from .const import DEFAULT_DATA -async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websocket): +async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_mock): """Test getting Plex clients from plex.tv.""" websocket_client = await hass_ws_client(hass) @@ -51,8 +51,10 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock result = msg["result"] assert result[ATTR_MEDIA_CONTENT_TYPE] == "server" assert result[ATTR_MEDIA_CONTENT_ID] == DEFAULT_DATA[CONF_SERVER_IDENTIFIER] - assert len(result["children"]) == len(mock_plex_server.library.sections()) + len( - SPECIAL_METHODS + # Library Sections + Special Sections + Playlists + assert ( + len(result["children"]) + == len(mock_plex_server.library.sections()) + len(SPECIAL_METHODS) + 1 ) tvshows = next(iter(x for x in result["children"] if x["title"] == "TV Shows")) @@ -149,9 +151,14 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock result = msg["result"] assert result[ATTR_MEDIA_CONTENT_TYPE] == "show" result_id = int(result[ATTR_MEDIA_CONTENT_ID]) - assert result["title"] == mock_plex_server.fetchItem(result_id).title + assert result["title"] == mock_plex_server.fetch_item(result_id).title # Browse into a non-existent TV season + unknown_key = 99999999999999 + requests_mock.get( + f"{mock_plex_server.url_in_use}/library/metadata/{unknown_key}", status_code=404 + ) + msg_id += 1 await websocket_client.send_json( { @@ -159,7 +166,7 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, mock_websock "type": "media_player/browse_media", "entity_id": media_players[0], ATTR_MEDIA_CONTENT_TYPE: result["children"][0][ATTR_MEDIA_CONTENT_TYPE], - ATTR_MEDIA_CONTENT_ID: str(99999999999999), + ATTR_MEDIA_CONTENT_ID: str(unknown_key), } ) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 13754a725db..bc0e59e658f 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -35,16 +35,11 @@ from homeassistant.const import ( CONF_URL, CONF_VERIFY_SSL, ) +from homeassistant.setup import async_setup_component -from .const import DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN +from .const import DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN, PLEX_DIRECT_URL from .helpers import trigger_plex_update, wait_for_debouncer -from .mock_classes import ( - MockGDM, - MockPlexAccount, - MockPlexClient, - MockPlexServer, - MockResource, -) +from .mock_classes import MockGDM from tests.common import MockConfigEntry @@ -82,7 +77,7 @@ async def test_bad_credentials(hass): assert result["errors"][CONF_TOKEN] == "faulty_credentials" -async def test_bad_hostname(hass): +async def test_bad_hostname(hass, mock_plex_calls): """Test when an invalid address is provided.""" await async_process_ha_core_config( hass, @@ -96,12 +91,9 @@ async def test_bad_hostname(hass): assert result["step_id"] == "user" with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() - ), patch.object( - MockResource, "connect", side_effect=requests.exceptions.ConnectionError - ), patch( - "plexauth.PlexAuth.initiate_auth" - ), patch( + "plexapi.myplex.MyPlexResource.connect", + side_effect=requests.exceptions.ConnectionError, + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -148,8 +140,9 @@ async def test_unknown_exception(hass): assert result["reason"] == "unknown" -async def test_no_servers_found(hass): +async def test_no_servers_found(hass, mock_plex_calls, requests_mock, empty_payload): """Test when no servers are on an account.""" + requests_mock.get("https://plex.tv/api/resources", text=empty_payload) await async_process_ha_core_config( hass, @@ -162,9 +155,7 @@ async def test_no_servers_found(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0) - ), patch("plexauth.PlexAuth.initiate_auth"), patch( + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -181,11 +172,9 @@ async def test_no_servers_found(hass): assert result["errors"]["base"] == "no_servers" -async def test_single_available_server(hass): +async def test_single_available_server(hass, mock_plex_calls): """Test creating an entry with one server available.""" - mock_plex_server = MockPlexServer() - await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, @@ -197,9 +186,7 @@ async def test_single_available_server(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), patch("plexauth.PlexAuth.initiate_auth"), patch( + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -212,20 +199,27 @@ async def test_single_available_server(hass): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName + + server_id = result["data"][CONF_SERVER_IDENTIFIER] + mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] + + assert result["title"] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( - result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] + == mock_plex_server.machine_identifier + ) + assert ( + result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use ) - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN -async def test_multiple_servers_with_selection(hass): +async def test_multiple_servers_with_selection( + hass, mock_plex_calls, requests_mock, plextv_resources_base +): """Test creating an entry with multiple servers available.""" - mock_plex_server = MockPlexServer() - await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, @@ -237,11 +231,11 @@ async def test_multiple_servers_with_selection(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "plexauth.PlexAuth.initiate_auth" - ), patch( + requests_mock.get( + "https://plex.tv/api/resources", + text=plextv_resources_base.format(second_server_enabled=1), + ) + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -261,20 +255,27 @@ async def test_multiple_servers_with_selection(hass): user_input={CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER]}, ) assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName + + server_id = result["data"][CONF_SERVER_IDENTIFIER] + mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] + + assert result["title"] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( - result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] + == mock_plex_server.machine_identifier + ) + assert ( + result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use ) - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN -async def test_adding_last_unconfigured_server(hass): +async def test_adding_last_unconfigured_server( + hass, mock_plex_calls, requests_mock, plextv_resources_base +): """Test automatically adding last unconfigured server when multiple servers on account.""" - mock_plex_server = MockPlexServer() - await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, @@ -294,11 +295,12 @@ async def test_adding_last_unconfigured_server(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "plexauth.PlexAuth.initiate_auth" - ), patch( + requests_mock.get( + "https://plex.tv/api/resources", + text=plextv_resources_base.format(second_server_enabled=1), + ) + + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -311,16 +313,25 @@ async def test_adding_last_unconfigured_server(hass): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName + + server_id = result["data"][CONF_SERVER_IDENTIFIER] + mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] + + assert result["title"] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( - result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] + == mock_plex_server.machine_identifier + ) + assert ( + result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use ) - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN -async def test_all_available_servers_configured(hass): +async def test_all_available_servers_configured( + hass, entry, requests_mock, plextv_account, plextv_resources_base +): """Test when all available servers are already configured.""" await async_process_ha_core_config( @@ -328,13 +339,7 @@ async def test_all_available_servers_configured(hass): {"internal_url": "http://example.local:8123"}, ) - MockConfigEntry( - domain=DOMAIN, - data={ - CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], - CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER], - }, - ).add_to_hass(hass) + entry.add_to_hass(hass) MockConfigEntry( domain=DOMAIN, @@ -350,9 +355,13 @@ async def test_all_available_servers_configured(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch("plexauth.PlexAuth.initiate_auth"), patch( + requests_mock.get("https://plex.tv/users/account", text=plextv_account) + requests_mock.get( + "https://plex.tv/api/resources", + text=plextv_resources_base.format(second_server_enabled=1), + ) + + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure( @@ -432,32 +441,22 @@ async def test_missing_option_flow(hass, entry, mock_plex_server): } -async def test_option_flow_new_users_available( - hass, caplog, entry, mock_websocket, setup_plex_server -): +async def test_option_flow_new_users_available(hass, entry, setup_plex_server): """Test config options multiselect defaults when new Plex users are seen.""" OPTIONS_OWNER_ONLY = copy.deepcopy(DEFAULT_OPTIONS) - OPTIONS_OWNER_ONLY[MP_DOMAIN][CONF_MONITORED_USERS] = {"Owner": {"enabled": True}} + OPTIONS_OWNER_ONLY[MP_DOMAIN][CONF_MONITORED_USERS] = {"User 1": {"enabled": True}} entry.options = OPTIONS_OWNER_ONLY - with patch("homeassistant.components.plex.server.PlexClient", new=MockPlexClient): - mock_plex_server = await setup_plex_server( - config_entry=entry, disable_gdm=False - ) - await hass.async_block_till_done() + mock_plex_server = await setup_plex_server(config_entry=entry) + await hass.async_block_till_done() - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users new_users = [x for x in mock_plex_server.accounts if x not in monitored_users] assert len(monitored_users) == 1 assert len(new_users) == 2 - await wait_for_debouncer(hass) - - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) - result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None ) @@ -465,7 +464,7 @@ async def test_option_flow_new_users_available( assert result["step_id"] == "plex_mp_settings" multiselect_defaults = result["data_schema"].schema["monitored_users"].options - assert "[Owner]" in multiselect_defaults["Owner"] + assert "[Owner]" in multiselect_defaults["User 1"] for user in new_users: assert "[New]" in multiselect_defaults[user] @@ -529,7 +528,7 @@ async def test_callback_view(hass, aiohttp_client): assert resp.status == 200 -async def test_manual_config(hass): +async def test_manual_config(hass, mock_plex_calls): """Test creating via manual configuration.""" await async_process_ha_core_config( hass, @@ -587,8 +586,6 @@ async def test_manual_config(hass): assert result["type"] == "form" assert result["step_id"] == "manual_setup" - mock_plex_server = MockPlexServer() - MANUAL_SERVER = { CONF_HOST: MOCK_SERVERS[0][CONF_HOST], CONF_PORT: MOCK_SERVERS[0][CONF_PORT], @@ -647,26 +644,26 @@ async def test_manual_config(hass): assert result["step_id"] == "manual_setup" assert result["errors"]["base"] == "ssl_error" - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket", autospec=True - ), patch( + with patch("homeassistant.components.plex.PlexWebsocket", autospec=True), patch( "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True) - ), patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MANUAL_SERVER ) assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl + + server_id = result["data"][CONF_SERVER_IDENTIFIER] + mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] + + assert result["title"] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier + assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN -async def test_manual_config_with_token(hass): +async def test_manual_config_with_token(hass, mock_plex_calls): """Test creating via manual configuration with only token.""" result = await hass.config_entries.flow.async_init( @@ -683,37 +680,36 @@ async def test_manual_config_with_token(hass): assert result["type"] == "form" assert result["step_id"] == "manual_setup" - mock_plex_server = MockPlexServer() - - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), patch( + with patch( "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True) - ), patch( - "homeassistant.components.plex.PlexWebsocket", autospec=True - ): + ), patch("homeassistant.components.plex.PlexWebsocket", autospec=True): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl + + server_id = result["data"][CONF_SERVER_IDENTIFIER] + mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] + + assert result["title"] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name + assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier + assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_setup_with_limited_credentials(hass, entry, setup_plex_server): """Test setup with a user with limited permissions.""" - with patch.object( - MockPlexServer, "systemAccounts", side_effect=plexapi.exceptions.Unauthorized + with patch( + "plexapi.server.PlexServer.systemAccounts", + side_effect=plexapi.exceptions.Unauthorized, ) as mock_accounts: mock_plex_server = await setup_plex_server() assert mock_accounts.called - plex_server = hass.data[DOMAIN][SERVERS][mock_plex_server.machineIdentifier] + plex_server = hass.data[DOMAIN][SERVERS][mock_plex_server.machine_identifier] assert len(plex_server.accounts) == 0 assert plex_server.owner is None @@ -745,6 +741,7 @@ async def test_integration_discovery(hass): async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket): """Test setup and reauthorization of a Plex token.""" + await async_setup_component(hass, "persistent_notification", {}) await async_process_ha_core_config( hass, {"internal_url": "http://example.local:8123"}, @@ -752,8 +749,8 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket): assert entry.state == ENTRY_STATE_LOADED - with patch.object( - mock_plex_server, "clients", side_effect=plexapi.exceptions.Unauthorized + with patch( + "plexapi.server.PlexServer.clients", side_effect=plexapi.exceptions.Unauthorized ), patch("plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized): trigger_plex_update(mock_websocket) await wait_for_debouncer(hass) @@ -767,9 +764,7 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket): flow_id = flows[0]["flow_id"] - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), patch("plexauth.PlexAuth.initiate_auth"), patch( + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN" ): result = await hass.config_entries.flow.async_configure(flow_id, user_input={}) @@ -787,7 +782,7 @@ async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket): assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state == ENTRY_STATE_LOADED - assert entry.data[CONF_SERVER] == mock_plex_server.friendlyName - assert entry.data[CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier - assert entry.data[PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl + assert entry.data[CONF_SERVER] == mock_plex_server.friendly_name + assert entry.data[CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier + assert entry.data[PLEX_SERVER_CONFIG][CONF_URL] == PLEX_DIRECT_URL assert entry.data[PLEX_SERVER_CONFIG][CONF_TOKEN] == "BRAND_NEW_TOKEN" diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index 404f7c167a5..95d2ef9bddb 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -15,11 +15,11 @@ from homeassistant.config_entries import ( ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, STATE_IDLE +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from .const import DEFAULT_DATA, DEFAULT_OPTIONS +from .const import DEFAULT_DATA, DEFAULT_OPTIONS, PLEX_DIRECT_URL from .helpers import trigger_plex_update, wait_for_debouncer -from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer from tests.common import MockConfigEntry, async_fire_time_changed @@ -31,7 +31,7 @@ async def test_set_config_entry_unique_id(hass, entry, mock_plex_server): assert ( hass.config_entries.async_entries(const.DOMAIN)[0].unique_id - == mock_plex_server.machineIdentifier + == mock_plex_server.machine_identifier ) @@ -79,9 +79,9 @@ async def test_unload_config_entry(hass, entry, mock_plex_server): assert entry is config_entries[0] assert entry.state == ENTRY_STATE_LOADED - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] - assert loaded_server.plex_server == mock_plex_server + assert loaded_server == mock_plex_server websocket = hass.data[const.DOMAIN][const.WEBSOCKETS][server_id] await hass.config_entries.async_unload(entry.entry_id) @@ -89,7 +89,7 @@ async def test_unload_config_entry(hass, entry, mock_plex_server): assert entry.state == ENTRY_STATE_NOT_LOADED -async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_server): +async def test_setup_with_photo_session(hass, entry, setup_plex_server): """Test setup component with config.""" await setup_plex_server(session_type="photo") @@ -97,7 +97,9 @@ async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_ assert entry.state == ENTRY_STATE_LOADED await hass.async_block_till_done() - media_player = hass.states.get("media_player.plex_product_title") + media_player = hass.states.get( + "media_player.plex_plex_for_android_tv_shield_android_tv" + ) assert media_player.state == STATE_IDLE await wait_for_debouncer(hass) @@ -106,14 +108,17 @@ async def test_setup_with_photo_session(hass, entry, mock_websocket, setup_plex_ assert sensor.state == "0" -async def test_setup_when_certificate_changed(hass, entry): +async def test_setup_when_certificate_changed( + hass, + requests_mock, + empty_payload, + plex_server_accounts, + plex_server_default, + plextv_account, + plextv_resources, +): """Test setup component when the Plex certificate has changed.""" - - old_domain = "1-2-3-4.1234567890abcdef1234567890abcdef.plex.direct" - old_url = f"https://{old_domain}:32400" - - OLD_HOSTNAME_DATA = copy.deepcopy(DEFAULT_DATA) - OLD_HOSTNAME_DATA[const.PLEX_SERVER_CONFIG][CONF_URL] = old_url + await async_setup_component(hass, "persistent_notification", {}) class WrongCertHostnameException(requests.exceptions.SSLError): """Mock the exception showing a mismatched hostname.""" @@ -123,6 +128,12 @@ async def test_setup_when_certificate_changed(hass, entry): f"hostname '{old_domain}' doesn't match" ) + old_domain = "1-2-3-4.1111111111ffffff1111111111ffffff.plex.direct" + old_url = f"https://{old_domain}:32400" + + OLD_HOSTNAME_DATA = copy.deepcopy(DEFAULT_DATA) + OLD_HOSTNAME_DATA[const.PLEX_SERVER_CONFIG][CONF_URL] = old_url + old_entry = MockConfigEntry( domain=const.DOMAIN, data=OLD_HOSTNAME_DATA, @@ -130,46 +141,45 @@ async def test_setup_when_certificate_changed(hass, entry): unique_id=DEFAULT_DATA["server_id"], ) + requests_mock.get("https://plex.tv/users/account", text=plextv_account) + requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) + requests_mock.get(old_url, exc=WrongCertHostnameException) + # Test with account failure - with patch( - "plexapi.server.PlexServer", side_effect=WrongCertHostnameException - ), patch( - "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized - ): - old_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(old_entry.entry_id) is False - await hass.async_block_till_done() + requests_mock.get(f"{old_url}/accounts", status_code=401) + old_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(old_entry.entry_id) is False + await hass.async_block_till_done() assert old_entry.state == ENTRY_STATE_SETUP_ERROR await hass.config_entries.async_unload(old_entry.entry_id) # Test with no servers found - with patch( - "plexapi.server.PlexServer", side_effect=WrongCertHostnameException - ), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0)): - assert await hass.config_entries.async_setup(old_entry.entry_id) is False - await hass.async_block_till_done() + requests_mock.get(f"{old_url}/accounts", text=plex_server_accounts) + requests_mock.get("https://plex.tv/api/resources", text=empty_payload) + + assert await hass.config_entries.async_setup(old_entry.entry_id) is False + await hass.async_block_till_done() assert old_entry.state == ENTRY_STATE_SETUP_ERROR await hass.config_entries.async_unload(old_entry.entry_id) # Test with success - with patch( - "plexapi.server.PlexServer", side_effect=WrongCertHostnameException - ), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - assert await hass.config_entries.async_setup(old_entry.entry_id) - await hass.async_block_till_done() + new_url = PLEX_DIRECT_URL + requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) + requests_mock.get(new_url, text=plex_server_default) + requests_mock.get(f"{new_url}/accounts", text=plex_server_accounts) + + assert await hass.config_entries.async_setup(old_entry.entry_id) + await hass.async_block_till_done() assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 assert old_entry.state == ENTRY_STATE_LOADED - assert ( - old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] - == entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] - ) + assert old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] == new_url -async def test_tokenless_server(hass, entry, mock_websocket, setup_plex_server): +async def test_tokenless_server(entry, setup_plex_server): """Test setup with a server with token auth disabled.""" TOKENLESS_DATA = copy.deepcopy(DEFAULT_DATA) TOKENLESS_DATA[const.PLEX_SERVER_CONFIG].pop(CONF_TOKEN, None) @@ -179,18 +189,13 @@ async def test_tokenless_server(hass, entry, mock_websocket, setup_plex_server): assert entry.state == ENTRY_STATE_LOADED -async def test_bad_token_with_tokenless_server(hass, entry): +async def test_bad_token_with_tokenless_server( + hass, entry, mock_websocket, setup_plex_server, requests_mock +): """Test setup with a bad token and a server with token auth disabled.""" - with patch("plexapi.server.PlexServer", return_value=MockPlexServer()), patch( - "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized - ), patch( - "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True) - ), patch( - "homeassistant.components.plex.PlexWebsocket", autospec=True - ) as mock_websocket: - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + requests_mock.get("https://plex.tv/users/account", status_code=401) + + await setup_plex_server() assert entry.state == ENTRY_STATE_LOADED diff --git a/tests/components/plex/test_media_players.py b/tests/components/plex/test_media_players.py index 3586cbc87bb..092d7e09008 100644 --- a/tests/components/plex/test_media_players.py +++ b/tests/components/plex/test_media_players.py @@ -3,39 +3,29 @@ from unittest.mock import patch from plexapi.exceptions import NotFound -from homeassistant.components.plex.const import DOMAIN, SERVERS - -async def test_plex_tv_clients(hass, entry, mock_plex_account, setup_plex_server): +async def test_plex_tv_clients( + hass, entry, setup_plex_server, requests_mock, player_plexweb_resources +): """Test getting Plex clients from plex.tv.""" - resource = next( - x - for x in mock_plex_account.resources() - if x.name.startswith("plex.tv Resource Player") - ) - with patch.object(resource, "connect", side_effect=NotFound): - mock_plex_server = await setup_plex_server() + requests_mock.get("/resources", text=player_plexweb_resources) + + with patch("plexapi.myplex.MyPlexResource.connect", side_effect=NotFound): + await setup_plex_server() await hass.async_block_till_done() - server_id = mock_plex_server.machineIdentifier - plex_server = hass.data[DOMAIN][SERVERS][server_id] media_players_before = len(hass.states.async_entity_ids("media_player")) + await hass.config_entries.async_unload(entry.entry_id) # Ensure one more client is discovered - await hass.config_entries.async_unload(entry.entry_id) - mock_plex_server = await setup_plex_server() - plex_server = hass.data[DOMAIN][SERVERS][server_id] + await setup_plex_server() media_players_after = len(hass.states.async_entity_ids("media_player")) assert media_players_after == media_players_before + 1 - # Ensure only plex.tv resource client is found await hass.config_entries.async_unload(entry.entry_id) - mock_plex_server = await setup_plex_server(num_users=0) - plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert len(hass.states.async_entity_ids("media_player")) == 1 - # Ensure cache gets called - await plex_server._async_update_platforms() - await hass.async_block_till_done() + # Ensure only plex.tv resource client is found + with patch("plexapi.server.PlexServer.sessions", return_value=[]): + await setup_plex_server(disable_clients=True) assert len(hass.states.async_entity_ids("media_player")) == 1 diff --git a/tests/components/plex/test_playback.py b/tests/components/plex/test_playback.py index 89c3f0253be..e8d528eb073 100644 --- a/tests/components/plex/test_playback.py +++ b/tests/components/plex/test_playback.py @@ -10,21 +10,25 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.components.plex.const import ( CONF_SERVER, + CONF_SERVER_IDENTIFIER, DOMAIN, + PLEX_SERVER_CONFIG, SERVERS, SERVICE_PLAY_ON_SONOS, ) -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, CONF_URL from homeassistant.exceptions import HomeAssistantError -from .const import DEFAULT_OPTIONS, SECONDARY_DATA +from .const import DEFAULT_OPTIONS, MOCK_SERVERS, SECONDARY_DATA from tests.common import MockConfigEntry -async def test_sonos_playback(hass, mock_plex_server): +async def test_sonos_playback( + hass, mock_plex_server, requests_mock, playqueue_created, sonos_resources +): """Test playing media on a Sonos speaker.""" - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier loaded_server = hass.data[DOMAIN][SERVERS][server_id] # Test Sonos integration lookup failure @@ -43,18 +47,23 @@ async def test_sonos_playback(hass, mock_plex_server): ) # Test success with plex_key + requests_mock.get("https://sonos.plex.tv/resources", text=sonos_resources) + requests_mock.get( + "https://sonos.plex.tv/player/playback/playMedia", status_code=200 + ) + requests_mock.post("/playqueues", text=playqueue_created) with patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", - ), patch("plexapi.playqueue.PlayQueue.create"): + return_value="Speaker 2", + ): assert await hass.services.async_call( DOMAIN, SERVICE_PLAY_ON_SONOS, { ATTR_ENTITY_ID: "media_player.sonos_kitchen", ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: "2", + ATTR_MEDIA_CONTENT_ID: "100", }, True, ) @@ -63,8 +72,8 @@ async def test_sonos_playback(hass, mock_plex_server): with patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", - ), patch("plexapi.playqueue.PlayQueue.create"): + return_value="Speaker 2", + ): assert await hass.services.async_call( DOMAIN, SERVICE_PLAY_ON_SONOS, @@ -80,8 +89,8 @@ async def test_sonos_playback(hass, mock_plex_server): with patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", - ), patch.object(mock_plex_server, "fetchItem", side_effect=NotFound): + return_value="Speaker 2", + ), patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound): assert await hass.services.async_call( DOMAIN, SERVICE_PLAY_ON_SONOS, @@ -97,7 +106,7 @@ async def test_sonos_playback(hass, mock_plex_server): with patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", + return_value="Speaker 2", ): assert await hass.services.async_call( DOMAIN, @@ -116,7 +125,7 @@ async def test_sonos_playback(hass, mock_plex_server): ), patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", + return_value="Speaker 2", ), patch( "plexapi.playqueue.PlayQueue.create" ): @@ -132,7 +141,17 @@ async def test_sonos_playback(hass, mock_plex_server): ) -async def test_playback_multiple_servers(hass, mock_websocket, setup_plex_server): +async def test_playback_multiple_servers( + hass, + setup_plex_server, + requests_mock, + caplog, + empty_payload, + playqueue_created, + plex_server_accounts, + plex_server_base, + sonos_resources, +): """Test playing media when multiple servers available.""" secondary_entry = MockConfigEntry( domain=DOMAIN, @@ -141,21 +160,60 @@ async def test_playback_multiple_servers(hass, mock_websocket, setup_plex_server unique_id=SECONDARY_DATA["server_id"], ) + secondary_url = SECONDARY_DATA[PLEX_SERVER_CONFIG][CONF_URL] + secondary_name = SECONDARY_DATA[CONF_SERVER] + secondary_id = SECONDARY_DATA[CONF_SERVER_IDENTIFIER] + requests_mock.get( + secondary_url, + text=plex_server_base.format( + name=secondary_name, machine_identifier=secondary_id + ), + ) + requests_mock.get(f"{secondary_url}/accounts", text=plex_server_accounts) + requests_mock.get(f"{secondary_url}/clients", text=empty_payload) + requests_mock.get(f"{secondary_url}/status/sessions", text=empty_payload) + await setup_plex_server() await setup_plex_server(config_entry=secondary_entry) + requests_mock.get("https://sonos.plex.tv/resources", text=sonos_resources) + requests_mock.get( + "https://sonos.plex.tv/player/playback/playMedia", status_code=200 + ) + requests_mock.post("/playqueues", text=playqueue_created) + with patch.object( hass.components.sonos, "get_coordinator_name", - return_value="media_player.sonos_kitchen", - ), patch("plexapi.playqueue.PlayQueue.create"): + return_value="Speaker 2", + ): assert await hass.services.async_call( DOMAIN, SERVICE_PLAY_ON_SONOS, { ATTR_ENTITY_ID: "media_player.sonos_kitchen", ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: f'{{"plex_server": "{SECONDARY_DATA[CONF_SERVER]}", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}}', + ATTR_MEDIA_CONTENT_ID: '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', + }, + True, + ) + + assert ( + "Multiple Plex servers configured, choose with 'plex_server' key" in caplog.text + ) + + with patch.object( + hass.components.sonos, + "get_coordinator_name", + return_value="Speaker 2", + ): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: f'{{"plex_server": "{MOCK_SERVERS[0][CONF_SERVER]}", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}}', }, True, ) diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index 2f8619834df..f9b34088601 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -28,29 +28,18 @@ from homeassistant.const import ATTR_ENTITY_ID from .const import DEFAULT_DATA, DEFAULT_OPTIONS from .helpers import trigger_plex_update, wait_for_debouncer -from .mock_classes import ( - MockPlexAccount, - MockPlexAlbum, - MockPlexArtist, - MockPlexLibrary, - MockPlexLibrarySection, - MockPlexMediaItem, - MockPlexSeason, - MockPlexServer, - MockPlexShow, -) -async def test_new_users_available(hass, entry, mock_websocket, setup_plex_server): +async def test_new_users_available(hass, entry, setup_plex_server): """Test setting up when new users available on Plex server.""" - MONITORED_USERS = {"Owner": {"enabled": True}} + MONITORED_USERS = {"User 1": {"enabled": True}} OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS) OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS entry.options = OPTIONS_WITH_USERS mock_plex_server = await setup_plex_server(config_entry=entry) - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users @@ -58,17 +47,18 @@ async def test_new_users_available(hass, entry, mock_websocket, setup_plex_serve assert len(monitored_users) == 1 assert len(ignored_users) == 0 - await wait_for_debouncer(hass) - - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) - async def test_new_ignored_users_available( - hass, caplog, entry, mock_websocket, setup_plex_server + hass, + caplog, + entry, + mock_websocket, + setup_plex_server, + requests_mock, + session_new_user, ): """Test setting up when new users available on Plex server but are ignored.""" - MONITORED_USERS = {"Owner": {"enabled": True}} + MONITORED_USERS = {"User 1": {"enabled": True}} OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS) OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS OPTIONS_WITH_USERS[MP_DOMAIN][CONF_IGNORE_NEW_SHARED_USERS] = True @@ -76,43 +66,50 @@ async def test_new_ignored_users_available( mock_plex_server = await setup_plex_server(config_entry=entry) - server_id = mock_plex_server.machineIdentifier + requests_mock.get( + f"{mock_plex_server.url_in_use}/status/sessions", + text=session_new_user, + ) + trigger_plex_update(mock_websocket) + await wait_for_debouncer(hass) + server_id = mock_plex_server.machine_identifier + + active_sessions = mock_plex_server._plex_server.sessions() monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users - ignored_users = [x for x in mock_plex_server.accounts if x not in monitored_users] + assert len(monitored_users) == 1 assert len(ignored_users) == 2 + for ignored_user in ignored_users: ignored_client = [ - x.players[0] - for x in mock_plex_server.sessions() - if x.usernames[0] == ignored_user - ][0] - assert ( - f"Ignoring {ignored_client.product} client owned by '{ignored_user}'" - in caplog.text - ) + x.players[0] for x in active_sessions if x.usernames[0] == ignored_user + ] + if ignored_client: + assert ( + f"Ignoring {ignored_client[0].product} client owned by '{ignored_user}'" + in caplog.text + ) await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) + assert sensor.state == str(len(active_sessions)) -async def test_network_error_during_refresh( - hass, caplog, mock_plex_server, mock_websocket -): +async def test_network_error_during_refresh(hass, caplog, mock_plex_server): """Test network failures during refreshes.""" - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier loaded_server = hass.data[DOMAIN][SERVERS][server_id] + active_sessions = mock_plex_server._plex_server.sessions() await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) + assert sensor.state == str(len(active_sessions)) - with patch.object(mock_plex_server, "clients", side_effect=RequestException): + with patch("plexapi.server.PlexServer.clients", side_effect=RequestException): await loaded_server._async_update_platforms() await hass.async_block_till_done() @@ -129,25 +126,31 @@ async def test_gdm_client_failure(hass, mock_websocket, setup_plex_server): mock_plex_server = await setup_plex_server(disable_gdm=False) await hass.async_block_till_done() + active_sessions = mock_plex_server._plex_server.sessions() await wait_for_debouncer(hass) sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) + assert sensor.state == str(len(active_sessions)) - with patch.object(mock_plex_server, "clients", side_effect=RequestException): + with patch("plexapi.server.PlexServer.clients", side_effect=RequestException): trigger_plex_update(mock_websocket) await hass.async_block_till_done() -async def test_mark_sessions_idle(hass, mock_plex_server, mock_websocket): +async def test_mark_sessions_idle( + hass, mock_plex_server, mock_websocket, requests_mock, empty_payload +): """Test marking media_players as idle when sessions end.""" await wait_for_debouncer(hass) - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) + active_sessions = mock_plex_server._plex_server.sessions() - mock_plex_server.clear_clients() - mock_plex_server.clear_sessions() + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(active_sessions)) + + url = mock_plex_server.url_in_use + requests_mock.get(f"{url}/clients", text=empty_payload) + requests_mock.get(f"{url}/status/sessions", text=empty_payload) trigger_plex_update(mock_websocket) await hass.async_block_till_done() @@ -157,43 +160,46 @@ async def test_mark_sessions_idle(hass, mock_plex_server, mock_websocket): assert sensor.state == "0" -async def test_ignore_plex_web_client(hass, entry, mock_websocket, setup_plex_server): +async def test_ignore_plex_web_client(hass, entry, setup_plex_server): """Test option to ignore Plex Web clients.""" OPTIONS = copy.deepcopy(DEFAULT_OPTIONS) OPTIONS[MP_DOMAIN][CONF_IGNORE_PLEX_WEB_CLIENTS] = True entry.options = OPTIONS - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)): - mock_plex_server = await setup_plex_server(config_entry=entry) - await wait_for_debouncer(hass) + mock_plex_server = await setup_plex_server( + config_entry=entry, client_type="plexweb", disable_clients=True + ) + await wait_for_debouncer(hass) + active_sessions = mock_plex_server._plex_server.sessions() sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) + assert sensor.state == str(len(active_sessions)) media_players = hass.states.async_entity_ids("media_player") assert len(media_players) == int(sensor.state) - 1 -async def test_media_lookups(hass, mock_plex_server, mock_websocket): +async def test_media_lookups(hass, mock_plex_server, requests_mock, playqueue_created): """Test media lookups to Plex server.""" - server_id = mock_plex_server.machineIdentifier + server_id = mock_plex_server.machine_identifier loaded_server = hass.data[DOMAIN][SERVERS][server_id] # Plex Key searches media_player_id = hass.states.async_entity_ids("media_player")[0] - with patch("homeassistant.components.plex.PlexServer.create_playqueue"): - assert await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: media_player_id, - ATTR_MEDIA_CONTENT_TYPE: DOMAIN, - ATTR_MEDIA_CONTENT_ID: 123, - }, - True, - ) - with patch.object(MockPlexServer, "fetchItem", side_effect=NotFound): + requests_mock.post("/playqueues", text=playqueue_created) + requests_mock.get("/player/playback/playMedia", status_code=200) + assert await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: media_player_id, + ATTR_MEDIA_CONTENT_TYPE: DOMAIN, + ATTR_MEDIA_CONTENT_ID: 1, + }, + True, + ) + with patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound): assert await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, @@ -206,20 +212,18 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket): ) # TV show searches - with patch.object(MockPlexLibrary, "section", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="TV Show" - ) - is None + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="TV Show" ) - with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show" - ) - is None + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show" ) + is None + ) assert ( loaded_server.lookup_media( MEDIA_TYPE_EPISODE, library_name="TV Shows", episode_name="An Episode" @@ -233,36 +237,34 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket): MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="TV Show", - season_number=2, + season_number=1, ) assert loaded_server.lookup_media( MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="TV Show", - season_number=2, + season_number=1, episode_number=3, ) - with patch.object(MockPlexShow, "season", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_EPISODE, - library_name="TV Shows", - show_name="TV Show", - season_number=2, - ) - is None + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="TV Show", + season_number=2, ) - with patch.object(MockPlexSeason, "episode", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_EPISODE, - library_name="TV Shows", - show_name="TV Show", - season_number=2, - episode_number=1, - ) - is None + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="TV Show", + season_number=2, + episode_number=1, ) + is None + ) # Music searches assert ( @@ -286,47 +288,43 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket): artist_name="Artist", album_name="Album", ) - with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MUSIC, - library_name="Music", - artist_name="Not an Artist", - album_name="Album", - ) - is None + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="Not an Artist", + album_name="Album", ) - with patch.object(MockPlexArtist, "album", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MUSIC, - library_name="Music", - artist_name="Artist", - album_name="Not an Album", - ) - is None + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="Artist", + album_name="Not an Album", ) - with patch.object(MockPlexAlbum, "track", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MUSIC, - library_name="Music", - artist_name="Artist", - album_name=" Album", - track_name="Not a Track", - ) - is None + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="Artist", + album_name=" Album", + track_name="Not a Track", ) - with patch.object(MockPlexArtist, "get", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MUSIC, - library_name="Music", - artist_name="Artist", - track_name="Not a Track", - ) - is None + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="Artist", + track_name="Not a Track", ) + is None + ) assert loaded_server.lookup_media( MEDIA_TYPE_MUSIC, library_name="Music", @@ -353,44 +351,33 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket): ) # Playlist searches - assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="A Playlist") + assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="Playlist 1") assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST) is None - with patch.object(MockPlexServer, "playlist", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist" - ) - is None - ) + assert ( + loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist") + is None + ) # Legacy Movie searches assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, video_name="Movie") is None assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, library_name="Movies") is None assert loaded_server.lookup_media( - MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Movie" + MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Movie 1" ) - with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie" - ) - is None + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie" ) + is None + ) # Movie searches assert loaded_server.lookup_media(MEDIA_TYPE_MOVIE, title="Movie") is None assert loaded_server.lookup_media(MEDIA_TYPE_MOVIE, library_name="Movies") is None assert loaded_server.lookup_media( - MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie" + MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie 1" ) - with patch.object(MockPlexLibrarySection, "search", side_effect=BadRequest): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MOVIE, library_name="Movies", title="Not a Movie" - ) - is None - ) - with patch.object(MockPlexLibrarySection, "search", return_value=[]): + with patch("plexapi.library.LibrarySection.search", side_effect=BadRequest): assert ( loaded_server.lookup_media( MEDIA_TYPE_MOVIE, library_name="Movies", title="Not a Movie" @@ -398,25 +385,8 @@ async def test_media_lookups(hass, mock_plex_server, mock_websocket): is None ) - similar_movies = [] - for title in "Duplicate Movie", "Duplicate Movie 2": - similar_movies.append(MockPlexMediaItem(title)) - with patch.object( - loaded_server.library.section("Movies"), "search", return_value=similar_movies - ): - found_media = loaded_server.lookup_media( - MEDIA_TYPE_MOVIE, library_name="Movies", title="Duplicate Movie" + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MOVIE, library_name="Movies", title="Movie" ) - assert found_media.title == "Duplicate Movie" - - duplicate_movies = [] - for title in "Duplicate Movie - Original", "Duplicate Movie - Remake": - duplicate_movies.append(MockPlexMediaItem(title)) - with patch.object( - loaded_server.library.section("Movies"), "search", return_value=duplicate_movies - ): - assert ( - loaded_server.lookup_media( - MEDIA_TYPE_MOVIE, library_name="Movies", title="Duplicate Movie" - ) - ) is None + ) is None diff --git a/tests/components/plex/test_services.py b/tests/components/plex/test_services.py index 06654a736c7..18375a9f80f 100644 --- a/tests/components/plex/test_services.py +++ b/tests/components/plex/test_services.py @@ -1,6 +1,4 @@ """Tests for various Plex services.""" -from unittest.mock import patch - from homeassistant.components.plex.const import ( CONF_SERVER, CONF_SERVER_IDENTIFIER, @@ -9,77 +7,84 @@ from homeassistant.components.plex.const import ( SERVICE_REFRESH_LIBRARY, SERVICE_SCAN_CLIENTS, ) -from homeassistant.const import ( - CONF_HOST, - CONF_PORT, - CONF_TOKEN, - CONF_URL, - CONF_VERIFY_SSL, -) +from homeassistant.const import CONF_URL -from .const import MOCK_SERVERS, MOCK_TOKEN -from .mock_classes import MockPlexLibrarySection +from .const import DEFAULT_OPTIONS, SECONDARY_DATA from tests.common import MockConfigEntry -async def test_refresh_library(hass, mock_plex_server, setup_plex_server): +async def test_refresh_library( + hass, + mock_plex_server, + setup_plex_server, + requests_mock, + empty_payload, + plex_server_accounts, + plex_server_base, +): """Test refresh_library service call.""" + url = mock_plex_server.url_in_use + refresh = requests_mock.get(f"{url}/library/sections/1/refresh", status_code=200) + # Test with non-existent server - with patch.object(MockPlexLibrarySection, "update") as mock_update: - assert await hass.services.async_call( - DOMAIN, - SERVICE_REFRESH_LIBRARY, - {"server_name": "Not a Server", "library_name": "Movies"}, - True, - ) - assert not mock_update.called + assert await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_LIBRARY, + {"server_name": "Not a Server", "library_name": "Movies"}, + True, + ) + assert not refresh.called # Test with non-existent library - with patch.object(MockPlexLibrarySection, "update") as mock_update: - assert await hass.services.async_call( - DOMAIN, - SERVICE_REFRESH_LIBRARY, - {"library_name": "Not a Library"}, - True, - ) - assert not mock_update.called + assert await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_LIBRARY, + {"library_name": "Not a Library"}, + True, + ) + assert not refresh.called # Test with valid library - with patch.object(MockPlexLibrarySection, "update") as mock_update: - assert await hass.services.async_call( - DOMAIN, - SERVICE_REFRESH_LIBRARY, - {"library_name": "Movies"}, - True, - ) - assert mock_update.called + assert await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_LIBRARY, + {"library_name": "Movies"}, + True, + ) + assert refresh.call_count == 1 # Add a second configured server + secondary_url = SECONDARY_DATA[PLEX_SERVER_CONFIG][CONF_URL] + secondary_name = SECONDARY_DATA[CONF_SERVER] + secondary_id = SECONDARY_DATA[CONF_SERVER_IDENTIFIER] + requests_mock.get( + secondary_url, + text=plex_server_base.format( + name=secondary_name, machine_identifier=secondary_id + ), + ) + requests_mock.get(f"{secondary_url}/accounts", text=plex_server_accounts) + requests_mock.get(f"{secondary_url}/clients", text=empty_payload) + requests_mock.get(f"{secondary_url}/status/sessions", text=empty_payload) + entry_2 = MockConfigEntry( domain=DOMAIN, - data={ - CONF_SERVER: MOCK_SERVERS[1][CONF_SERVER], - PLEX_SERVER_CONFIG: { - CONF_TOKEN: MOCK_TOKEN, - CONF_URL: f"https://{MOCK_SERVERS[1][CONF_HOST]}:{MOCK_SERVERS[1][CONF_PORT]}", - CONF_VERIFY_SSL: True, - }, - CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][CONF_SERVER_IDENTIFIER], - }, + data=SECONDARY_DATA, + options=DEFAULT_OPTIONS, + unique_id=SECONDARY_DATA["server_id"], ) await setup_plex_server(config_entry=entry_2) # Test multiple servers available but none specified - with patch.object(MockPlexLibrarySection, "update") as mock_update: - assert await hass.services.async_call( - DOMAIN, - SERVICE_REFRESH_LIBRARY, - {"library_name": "Movies"}, - True, - ) - assert not mock_update.called + assert await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_LIBRARY, + {"library_name": "Movies"}, + True, + ) + assert refresh.call_count == 1 async def test_scan_clients(hass, mock_plex_server): diff --git a/tests/fixtures/plex/album.xml b/tests/fixtures/plex/album.xml new file mode 100644 index 00000000000..380149cf5ac --- /dev/null +++ b/tests/fixtures/plex/album.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/artist_albums.xml b/tests/fixtures/plex/artist_albums.xml new file mode 100644 index 00000000000..b1c8d1afb89 --- /dev/null +++ b/tests/fixtures/plex/artist_albums.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/children_20.xml b/tests/fixtures/plex/children_20.xml new file mode 100644 index 00000000000..6f433fff9a8 --- /dev/null +++ b/tests/fixtures/plex/children_20.xml @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/plex/children_200.xml b/tests/fixtures/plex/children_200.xml new file mode 100644 index 00000000000..e1ff4934651 --- /dev/null +++ b/tests/fixtures/plex/children_200.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/children_30.xml b/tests/fixtures/plex/children_30.xml new file mode 100644 index 00000000000..bf87607f0b0 --- /dev/null +++ b/tests/fixtures/plex/children_30.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/children_300.xml b/tests/fixtures/plex/children_300.xml new file mode 100644 index 00000000000..b1c8d1afb89 --- /dev/null +++ b/tests/fixtures/plex/children_300.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/empty_library.xml b/tests/fixtures/plex/empty_library.xml new file mode 100644 index 00000000000..853d3b9791f --- /dev/null +++ b/tests/fixtures/plex/empty_library.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/empty_payload.xml b/tests/fixtures/plex/empty_payload.xml new file mode 100644 index 00000000000..89bcdba2d58 --- /dev/null +++ b/tests/fixtures/plex/empty_payload.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/grandchildren_300.xml b/tests/fixtures/plex/grandchildren_300.xml new file mode 100644 index 00000000000..2c9741e2c1b --- /dev/null +++ b/tests/fixtures/plex/grandchildren_300.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/library.xml b/tests/fixtures/plex/library.xml new file mode 100644 index 00000000000..4d6ec69990b --- /dev/null +++ b/tests/fixtures/plex/library.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/fixtures/plex/library_movies_all.xml b/tests/fixtures/plex/library_movies_all.xml new file mode 100644 index 00000000000..cd194040b37 --- /dev/null +++ b/tests/fixtures/plex/library_movies_all.xml @@ -0,0 +1,51 @@ + diff --git a/tests/fixtures/plex/library_movies_sort.xml b/tests/fixtures/plex/library_movies_sort.xml new file mode 100644 index 00000000000..052eac3590a --- /dev/null +++ b/tests/fixtures/plex/library_movies_sort.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/fixtures/plex/library_music_all.xml b/tests/fixtures/plex/library_music_all.xml new file mode 100644 index 00000000000..6676817780d --- /dev/null +++ b/tests/fixtures/plex/library_music_all.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/plex/library_music_sort.xml b/tests/fixtures/plex/library_music_sort.xml new file mode 100644 index 00000000000..3a516a2a2f8 --- /dev/null +++ b/tests/fixtures/plex/library_music_sort.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/fixtures/plex/library_sections.xml b/tests/fixtures/plex/library_sections.xml new file mode 100644 index 00000000000..954af4b6928 --- /dev/null +++ b/tests/fixtures/plex/library_sections.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/tests/fixtures/plex/library_tvshows_all.xml b/tests/fixtures/plex/library_tvshows_all.xml new file mode 100644 index 00000000000..e734d396ca2 --- /dev/null +++ b/tests/fixtures/plex/library_tvshows_all.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/plex/library_tvshows_sort.xml b/tests/fixtures/plex/library_tvshows_sort.xml new file mode 100644 index 00000000000..63df4738a24 --- /dev/null +++ b/tests/fixtures/plex/library_tvshows_sort.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/fixtures/plex/media_1.xml b/tests/fixtures/plex/media_1.xml new file mode 100644 index 00000000000..838afb2959c --- /dev/null +++ b/tests/fixtures/plex/media_1.xml @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/plex/media_100.xml b/tests/fixtures/plex/media_100.xml new file mode 100644 index 00000000000..e1326a4c862 --- /dev/null +++ b/tests/fixtures/plex/media_100.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/media_200.xml b/tests/fixtures/plex/media_200.xml new file mode 100644 index 00000000000..380149cf5ac --- /dev/null +++ b/tests/fixtures/plex/media_200.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/media_30.xml b/tests/fixtures/plex/media_30.xml new file mode 100644 index 00000000000..14a69adc0c7 --- /dev/null +++ b/tests/fixtures/plex/media_30.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/plex/player_plexweb_resources.xml b/tests/fixtures/plex/player_plexweb_resources.xml new file mode 100644 index 00000000000..f3a2e31335a --- /dev/null +++ b/tests/fixtures/plex/player_plexweb_resources.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/playlist_500.xml b/tests/fixtures/plex/playlist_500.xml new file mode 100644 index 00000000000..d1d008549e8 --- /dev/null +++ b/tests/fixtures/plex/playlist_500.xml @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/plex/playlists.xml b/tests/fixtures/plex/playlists.xml new file mode 100644 index 00000000000..bc0dd69905e --- /dev/null +++ b/tests/fixtures/plex/playlists.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/plex/playqueue_created.xml b/tests/fixtures/plex/playqueue_created.xml new file mode 100644 index 00000000000..72a274ca7b9 --- /dev/null +++ b/tests/fixtures/plex/playqueue_created.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/plex_server_accounts.xml b/tests/fixtures/plex/plex_server_accounts.xml new file mode 100644 index 00000000000..22b92d89c4a --- /dev/null +++ b/tests/fixtures/plex/plex_server_accounts.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/plex/plex_server_base.xml b/tests/fixtures/plex/plex_server_base.xml new file mode 100644 index 00000000000..da983d2f356 --- /dev/null +++ b/tests/fixtures/plex/plex_server_base.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/plex/plex_server_clients.xml b/tests/fixtures/plex/plex_server_clients.xml new file mode 100644 index 00000000000..c7f6180e9c3 --- /dev/null +++ b/tests/fixtures/plex/plex_server_clients.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/plex/plextv_account.xml b/tests/fixtures/plex/plextv_account.xml new file mode 100644 index 00000000000..32d6eec7c2d --- /dev/null +++ b/tests/fixtures/plex/plextv_account.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + testuser + testuser@email.com + 2000-01-01 12:34:56 UTC + faketoken + diff --git a/tests/fixtures/plex/plextv_resources_base.xml b/tests/fixtures/plex/plextv_resources_base.xml new file mode 100644 index 00000000000..41e61711d36 --- /dev/null +++ b/tests/fixtures/plex/plextv_resources_base.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/plex/security_token.xml b/tests/fixtures/plex/security_token.xml new file mode 100644 index 00000000000..1d7bde66fa6 --- /dev/null +++ b/tests/fixtures/plex/security_token.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/plex/session_base.xml b/tests/fixtures/plex/session_base.xml new file mode 100644 index 00000000000..e7451e93af4 --- /dev/null +++ b/tests/fixtures/plex/session_base.xml @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/plex/session_photo.xml b/tests/fixtures/plex/session_photo.xml new file mode 100644 index 00000000000..952875e525e --- /dev/null +++ b/tests/fixtures/plex/session_photo.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/fixtures/plex/session_plexweb.xml b/tests/fixtures/plex/session_plexweb.xml new file mode 100644 index 00000000000..40597d7b701 --- /dev/null +++ b/tests/fixtures/plex/session_plexweb.xml @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/plex/show_seasons.xml b/tests/fixtures/plex/show_seasons.xml new file mode 100644 index 00000000000..bf87607f0b0 --- /dev/null +++ b/tests/fixtures/plex/show_seasons.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/plex/sonos_resources.xml b/tests/fixtures/plex/sonos_resources.xml new file mode 100644 index 00000000000..334fdd311ef --- /dev/null +++ b/tests/fixtures/plex/sonos_resources.xml @@ -0,0 +1,5 @@ + + + + +