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 @@
+
+
+
+
+