From 44c774335197d63a752ba08dbdf713741310b1aa Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 11 Mar 2020 11:37:02 -0500 Subject: [PATCH] Rewrite and add Plex tests (#32633) * Rewrite and add Plex tests * Remove unnecessary mocks * Explicitly import constants for readability --- .coveragerc | 2 - homeassistant/components/plex/media_player.py | 1 + tests/components/plex/const.py | 52 +++ tests/components/plex/mock_classes.py | 235 ++++++++---- tests/components/plex/test_config_flow.py | 351 +++++++----------- tests/components/plex/test_init.py | 302 +++++++++++++++ tests/components/plex/test_server.py | 134 +++++++ 7 files changed, 788 insertions(+), 289 deletions(-) create mode 100644 tests/components/plex/const.py create mode 100644 tests/components/plex/test_init.py create mode 100644 tests/components/plex/test_server.py diff --git a/.coveragerc b/.coveragerc index f1f1f0e6222..dd89a4dfd26 100644 --- a/.coveragerc +++ b/.coveragerc @@ -542,10 +542,8 @@ omit = homeassistant/components/pioneer/media_player.py homeassistant/components/pjlink/media_player.py homeassistant/components/plaato/* - homeassistant/components/plex/__init__.py homeassistant/components/plex/media_player.py homeassistant/components/plex/sensor.py - homeassistant/components/plex/server.py homeassistant/components/plugwise/* homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 0599837aa80..1be06876baf 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -333,6 +333,7 @@ class PlexMediaPlayer(MediaPlayerDevice): def force_idle(self): """Force client to idle.""" + self._player_state = STATE_IDLE self._state = STATE_IDLE self.session = None self._clear_media_details() diff --git a/tests/components/plex/const.py b/tests/components/plex/const.py new file mode 100644 index 00000000000..0f91a9da23f --- /dev/null +++ b/tests/components/plex/const.py @@ -0,0 +1,52 @@ +"""Constants used by Plex tests.""" +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.plex import const +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + CONF_TOKEN, + CONF_URL, + CONF_VERIFY_SSL, +) + +MOCK_SERVERS = [ + { + CONF_HOST: "1.2.3.4", + CONF_PORT: 32400, + const.CONF_SERVER: "Plex Server 1", + const.CONF_SERVER_IDENTIFIER: "unique_id_123", + }, + { + CONF_HOST: "4.3.2.1", + CONF_PORT: 32400, + const.CONF_SERVER: "Plex Server 2", + const.CONF_SERVER_IDENTIFIER: "unique_id_456", + }, +] + +MOCK_USERS = { + "Owner": {"enabled": True}, + "b": {"enabled": True}, + "c": {"enabled": True}, +} + +MOCK_TOKEN = "secret_token" + +DEFAULT_DATA = { + const.CONF_SERVER: MOCK_SERVERS[0][const.CONF_SERVER], + const.PLEX_SERVER_CONFIG: { + const.CONF_CLIENT_IDENTIFIER: "00000000-0000-0000-0000-000000000000", + CONF_TOKEN: MOCK_TOKEN, + CONF_URL: f"https://{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}", + CONF_VERIFY_SSL: True, + }, + const.CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][const.CONF_SERVER_IDENTIFIER], +} + +DEFAULT_OPTIONS = { + MP_DOMAIN: { + const.CONF_IGNORE_NEW_SHARED_USERS: False, + const.CONF_MONITORED_USERS: MOCK_USERS, + const.CONF_USE_EPISODE_ART: False, + } +} diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 6e61dfac3ab..9b59190173f 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -1,29 +1,12 @@ """Mock classes used in tests.""" -import itertools +from homeassistant.components.plex.const import ( + CONF_SERVER, + CONF_SERVER_IDENTIFIER, + PLEX_SERVER_CONFIG, +) +from homeassistant.const import CONF_URL -from homeassistant.components.plex.const import CONF_SERVER, CONF_SERVER_IDENTIFIER -from homeassistant.const import CONF_HOST, CONF_PORT - -MOCK_SERVERS = [ - { - CONF_HOST: "1.2.3.4", - CONF_PORT: 32400, - CONF_SERVER: "Plex Server 1", - CONF_SERVER_IDENTIFIER: "unique_id_123", - }, - { - CONF_HOST: "4.3.2.1", - CONF_PORT: 32400, - CONF_SERVER: "Plex Server 2", - CONF_SERVER_IDENTIFIER: "unique_id_456", - }, -] - -MOCK_MONITORED_USERS = { - "a": {"enabled": True}, - "b": {"enabled": False}, - "c": {"enabled": True}, -} +from .const import DEFAULT_DATA, MOCK_SERVERS, MOCK_USERS class MockResource: @@ -64,10 +47,11 @@ class MockPlexAccount: class MockPlexSystemAccount: """Mock a PlexSystemAccount instance.""" - def __init__(self): + def __init__(self, index): """Initialize the object.""" - self.name = "Dummy" - self.accountID = 1 + # Start accountIDs at 1 to set proper owner. + self.name = list(MOCK_USERS)[index] + self.accountID = index + 1 class MockPlexServer: @@ -76,68 +60,179 @@ class MockPlexServer: def __init__( self, index=0, - ssl=True, - load_users=True, - num_users=len(MOCK_MONITORED_USERS), - ignore_new_users=False, + config_entry=None, + num_users=len(MOCK_USERS), + session_type="video", ): """Initialize the object.""" - host = MOCK_SERVERS[index][CONF_HOST] - port = MOCK_SERVERS[index][CONF_PORT] - self.friendlyName = MOCK_SERVERS[index][ # pylint: disable=invalid-name - CONF_SERVER + 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._sessions = [] + self.set_clients(num_users) + self.set_sessions(num_users, session_type) + + def set_clients(self, num_clients): + """Set up mock PlexClients for this PlexServer.""" + self._clients = [MockPlexClient(self._baseurl, 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) ] - self.machineIdentifier = MOCK_SERVERS[index][ # pylint: disable=invalid-name - CONF_SERVER_IDENTIFIER - ] - prefix = "https" if ssl else "http" - self._baseurl = f"{prefix}://{host}:{port}" - self._systemAccount = MockPlexSystemAccount() - self._ignore_new_users = ignore_new_users - self._load_users = load_users - self._num_users = num_users + + 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 sessions(self): + """Mock the sessions method.""" + return self._sessions def systemAccounts(self): """Mock the systemAccounts lookup method.""" - return [self._systemAccount] + 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(["a", "b", "c"]) - - @property - def owner(self): - """Mock the owner property.""" - return "a" - - @property - def url_in_use(self): - """Return URL used by PlexServer.""" - return self._baseurl + return set(MOCK_USERS) @property def version(self): """Mock version of PlexServer.""" return "1.0" - @property - def option_monitored_users(self): - """Mock loaded config option for monitored users.""" - userdict = dict(itertools.islice(MOCK_MONITORED_USERS.items(), self._num_users)) - return userdict if self._load_users else {} + +class MockPlexClient: + """Mock a PlexClient instance.""" + + def __init__(self, url, index=0): + """Initialize the object.""" + self.machineIdentifier = f"client-{index+1}" + self._baseurl = url + + def url(self, key): + """Mock the url method.""" + return f"{self._baseurl}{key}" @property - def option_ignore_new_shared_users(self): - """Mock loaded config option for ignoring new users.""" - return self._ignore_new_users + def device(self): + """Mock the device attribute.""" + return "DEVICE" @property - def option_show_all_controls(self): - """Mock loaded config option for showing all controls.""" - return False + def platform(self): + """Mock the platform attribute.""" + return "PLATFORM" @property - def option_use_episode_art(self): - """Mock loaded config option for using episode art.""" - return False + def product(self): + """Mock the product attribute.""" + return "PRODUCT" + + @property + def protocolCapabilities(self): + """Mock the protocolCapabilities attribute.""" + return ["player"] + + @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" + + +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() + + @property + def duration(self): + """Mock the duration attribute.""" + return 10000000 + + @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 MockPlexLibrarySection: + """Mock a Plex LibrarySection instance.""" + + def __init__(self, library="Movies"): + """Initialize the object.""" + self.title = library diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index c131a123dc9..d839ccc674b 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -1,55 +1,46 @@ """Tests for Plex config flow.""" import copy -from unittest.mock import patch -import asynctest +from asynctest import patch import plexapi.exceptions import requests.exceptions +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.plex import config_flow +from homeassistant.components.plex.const import ( + CONF_IGNORE_NEW_SHARED_USERS, + CONF_MONITORED_USERS, + CONF_SERVER, + CONF_SERVER_IDENTIFIER, + CONF_USE_EPISODE_ART, + DOMAIN, + PLEX_SERVER_CONFIG, + PLEX_UPDATE_PLATFORMS_SIGNAL, + SERVERS, +) +from homeassistant.config_entries import ENTRY_STATE_LOADED from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from .mock_classes import MOCK_SERVERS, MockPlexAccount, MockPlexServer +from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN +from .mock_classes import MockPlexAccount, MockPlexServer from tests.common import MockConfigEntry -MOCK_TOKEN = "secret_token" -MOCK_FILE_CONTENTS = { - f"{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}": { - "ssl": False, - "token": MOCK_TOKEN, - "verify": True, - } -} - -DEFAULT_OPTIONS = { - config_flow.MP_DOMAIN: { - config_flow.CONF_USE_EPISODE_ART: False, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: False, - } -} - - -def init_config_flow(hass): - """Init a configuration flow.""" - flow = config_flow.PlexFlowHandler() - flow.hass = hass - return flow - async def test_bad_credentials(hass): """Test when provided credentials are rejected.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch( "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value="BAD TOKEN" ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -72,7 +63,7 @@ async def test_import_success(hass): with patch("plexapi.server.PlexServer", return_value=mock_plex_server): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + DOMAIN, context={"source": "import"}, data={ CONF_TOKEN: MOCK_TOKEN, @@ -82,16 +73,10 @@ async def test_import_success(hass): assert result["type"] == "create_entry" assert result["title"] == mock_plex_server.friendlyName - assert result["data"][config_flow.CONF_SERVER] == mock_plex_server.friendlyName - assert ( - result["data"][config_flow.CONF_SERVER_IDENTIFIER] - == mock_plex_server.machineIdentifier - ) - assert ( - result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] - == mock_plex_server.url_in_use - ) - assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN + 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 + assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_import_bad_hostname(hass): @@ -101,7 +86,7 @@ async def test_import_bad_hostname(hass): "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError ): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + DOMAIN, context={"source": "import"}, data={ CONF_TOKEN: MOCK_TOKEN, @@ -116,14 +101,14 @@ async def test_unknown_exception(hass): """Test when an unknown exception is encountered.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" - with patch("plexapi.myplex.MyPlexAccount", side_effect=Exception), asynctest.patch( + with patch("plexapi.myplex.MyPlexAccount", side_effect=Exception), patch( "plexauth.PlexAuth.initiate_auth" - ), asynctest.patch("plexauth.PlexAuth.token", return_value="MOCK_TOKEN"): + ), patch("plexauth.PlexAuth.token", return_value="MOCK_TOKEN"): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "external" @@ -141,14 +126,14 @@ async def test_no_servers_found(hass): await async_setup_component(hass, "http", {"http": {}}) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch( "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0) - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -171,14 +156,14 @@ async def test_single_available_server(hass): await async_setup_component(hass, "http", {"http": {}}) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch( "plexapi.server.PlexServer", return_value=mock_plex_server - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -190,16 +175,12 @@ 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"][config_flow.CONF_SERVER] == mock_plex_server.friendlyName + assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName assert ( - result["data"][config_flow.CONF_SERVER_IDENTIFIER] - == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier ) - assert ( - result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] - == mock_plex_server.url_in_use - ) - assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN + 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): @@ -210,18 +191,16 @@ async def test_multiple_servers_with_selection(hass): await async_setup_component(hass, "http", {"http": {}}) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch( "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), asynctest.patch( + ), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( "plexauth.PlexAuth.initiate_auth" - ), asynctest.patch( + ), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -235,23 +214,16 @@ async def test_multiple_servers_with_selection(hass): assert result["step_id"] == "select_server" result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - config_flow.CONF_SERVER: MOCK_SERVERS[0][config_flow.CONF_SERVER] - }, + result["flow_id"], user_input={CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER]}, ) assert result["type"] == "create_entry" assert result["title"] == mock_plex_server.friendlyName - assert result["data"][config_flow.CONF_SERVER] == mock_plex_server.friendlyName + assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName assert ( - result["data"][config_flow.CONF_SERVER_IDENTIFIER] - == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier ) - assert ( - result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] - == mock_plex_server.url_in_use - ) - assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN + 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): @@ -262,28 +234,24 @@ async def test_adding_last_unconfigured_server(hass): await async_setup_component(hass, "http", {"http": {}}) MockConfigEntry( - domain=config_flow.DOMAIN, + domain=DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][ - config_flow.CONF_SERVER_IDENTIFIER - ], - config_flow.CONF_SERVER: MOCK_SERVERS[1][config_flow.CONF_SERVER], + CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][CONF_SERVER_IDENTIFIER], + CONF_SERVER: MOCK_SERVERS[1][CONF_SERVER], }, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch( "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), asynctest.patch( + ), patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( "plexauth.PlexAuth.initiate_auth" - ), asynctest.patch( + ), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -295,16 +263,12 @@ 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"][config_flow.CONF_SERVER] == mock_plex_server.friendlyName + assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName assert ( - result["data"][config_flow.CONF_SERVER_IDENTIFIER] - == mock_plex_server.machineIdentifier + result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier ) - assert ( - result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] - == mock_plex_server.url_in_use - ) - assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN + 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_already_configured(hass): @@ -313,23 +277,19 @@ async def test_already_configured(hass): mock_plex_server = MockPlexServer() MockConfigEntry( - domain=config_flow.DOMAIN, + domain=DOMAIN, data={ - config_flow.CONF_SERVER: MOCK_SERVERS[0][config_flow.CONF_SERVER], - config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][ - config_flow.CONF_SERVER_IDENTIFIER - ], + CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER], + CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], }, - unique_id=MOCK_SERVERS[0][config_flow.CONF_SERVER_IDENTIFIER], + unique_id=MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], ).add_to_hass(hass) - with patch( - "plexapi.server.PlexServer", return_value=mock_plex_server - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN - ): + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "plexauth.PlexAuth.initiate_auth" + ), patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + DOMAIN, context={"source": "import"}, data={ CONF_TOKEN: MOCK_TOKEN, @@ -346,34 +306,30 @@ async def test_all_available_servers_configured(hass): await async_setup_component(hass, "http", {"http": {}}) MockConfigEntry( - domain=config_flow.DOMAIN, + domain=DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][ - config_flow.CONF_SERVER_IDENTIFIER - ], - config_flow.CONF_SERVER: MOCK_SERVERS[0][config_flow.CONF_SERVER], + CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], + CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER], }, ).add_to_hass(hass) MockConfigEntry( - domain=config_flow.DOMAIN, + domain=DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][ - config_flow.CONF_SERVER_IDENTIFIER - ], - config_flow.CONF_SERVER: MOCK_SERVERS[1][config_flow.CONF_SERVER], + CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][CONF_SERVER_IDENTIFIER], + CONF_SERVER: MOCK_SERVERS[1][CONF_SERVER], }, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" with patch( "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -389,20 +345,26 @@ async def test_all_available_servers_configured(hass): async def test_option_flow(hass): """Test config options flow selection.""" - - mock_plex_server = MockPlexServer(load_users=False) - - MOCK_SERVER_ID = MOCK_SERVERS[0][config_flow.CONF_SERVER_IDENTIFIER] - hass.data[config_flow.DOMAIN] = { - config_flow.SERVERS: {MOCK_SERVER_ID: mock_plex_server} - } + mock_plex_server = MockPlexServer() entry = MockConfigEntry( - domain=config_flow.DOMAIN, - data={config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVER_ID}, + domain=DOMAIN, + data=DEFAULT_DATA, options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], ) - entry.add_to_hass(hass) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert mock_listen.called + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None @@ -413,112 +375,69 @@ async def test_option_flow(hass): result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: list(mock_plex_server.accounts), + CONF_USE_EPISODE_ART: True, + CONF_IGNORE_NEW_SHARED_USERS: True, + CONF_MONITORED_USERS: list(mock_plex_server.accounts), }, ) assert result["type"] == "create_entry" assert result["data"] == { - config_flow.MP_DOMAIN: { - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: { + MP_DOMAIN: { + CONF_USE_EPISODE_ART: True, + CONF_IGNORE_NEW_SHARED_USERS: True, + CONF_MONITORED_USERS: { user: {"enabled": True} for user in mock_plex_server.accounts }, } } -async def test_option_flow_loading_saved_users(hass): - """Test config options flow selection when loading existing user config.""" +async def test_option_flow_new_users_available(hass, caplog): + """Test config options multiselect defaults when new Plex users are seen.""" - mock_plex_server = MockPlexServer(load_users=True) - - MOCK_SERVER_ID = MOCK_SERVERS[0][config_flow.CONF_SERVER_IDENTIFIER] - hass.data[config_flow.DOMAIN] = { - config_flow.SERVERS: {MOCK_SERVER_ID: mock_plex_server} - } + OPTIONS_OWNER_ONLY = copy.deepcopy(DEFAULT_OPTIONS) + OPTIONS_OWNER_ONLY[MP_DOMAIN][CONF_MONITORED_USERS] = {"Owner": {"enabled": True}} entry = MockConfigEntry( - domain=config_flow.DOMAIN, - data={config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVER_ID}, - options=DEFAULT_OPTIONS, + domain=DOMAIN, + data=DEFAULT_DATA, + options=OPTIONS_OWNER_ONLY, + unique_id=DEFAULT_DATA["server_id"], ) - entry.add_to_hass(hass) + + mock_plex_server = MockPlexServer(config_entry=entry) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + 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 + + 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 ) assert result["type"] == "form" assert result["step_id"] == "plex_mp_settings" + multiselect_defaults = result["data_schema"].schema["monitored_users"].options - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: list(mock_plex_server.accounts), - }, - ) - assert result["type"] == "create_entry" - assert result["data"] == { - config_flow.MP_DOMAIN: { - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: { - user: {"enabled": True} for user in mock_plex_server.accounts - }, - } - } - - -async def test_option_flow_new_users_available(hass): - """Test config options flow selection when new Plex accounts available.""" - - mock_plex_server = MockPlexServer(load_users=True, num_users=2) - - MOCK_SERVER_ID = MOCK_SERVERS[0][config_flow.CONF_SERVER_IDENTIFIER] - hass.data[config_flow.DOMAIN] = { - config_flow.SERVERS: {MOCK_SERVER_ID: mock_plex_server} - } - - OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS) - OPTIONS_WITH_USERS[config_flow.MP_DOMAIN][config_flow.CONF_MONITORED_USERS] = { - "a": {"enabled": True} - } - - entry = MockConfigEntry( - domain=config_flow.DOMAIN, - data={config_flow.CONF_SERVER_IDENTIFIER: MOCK_SERVER_ID}, - options=OPTIONS_WITH_USERS, - ) - entry.add_to_hass(hass) - - result = await hass.config_entries.options.async_init( - entry.entry_id, context={"source": "test"}, data=None - ) - assert result["type"] == "form" - assert result["step_id"] == "plex_mp_settings" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: list(mock_plex_server.accounts), - }, - ) - assert result["type"] == "create_entry" - assert result["data"] == { - config_flow.MP_DOMAIN: { - config_flow.CONF_USE_EPISODE_ART: True, - config_flow.CONF_IGNORE_NEW_SHARED_USERS: True, - config_flow.CONF_MONITORED_USERS: { - user: {"enabled": True} for user in mock_plex_server.accounts - }, - } - } + assert "[Owner]" in multiselect_defaults["Owner"] + for user in new_users: + assert "[New]" in multiselect_defaults[user] async def test_external_timed_out(hass): @@ -527,12 +446,12 @@ async def test_external_timed_out(hass): await async_setup_component(hass, "http", {"http": {}}) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" - with asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=None ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -552,12 +471,12 @@ async def test_callback_view(hass, aiohttp_client): await async_setup_component(hass, "http", {"http": {}}) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + DOMAIN, context={"source": "user"} ) assert result["type"] == "form" assert result["step_id"] == "start_website_auth" - with asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + with patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -575,13 +494,11 @@ async def test_multiple_servers_with_import(hass): with patch( "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch( + ), patch("plexauth.PlexAuth.initiate_auth"), patch( "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": "import"}, - data={CONF_TOKEN: MOCK_TOKEN}, + DOMAIN, context={"source": "import"}, data={CONF_TOKEN: MOCK_TOKEN}, ) assert result["type"] == "abort" assert result["reason"] == "non-interactive" diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py new file mode 100644 index 00000000000..3358ac1c2cb --- /dev/null +++ b/tests/components/plex/test_init.py @@ -0,0 +1,302 @@ +"""Tests for Plex setup.""" +import copy +from datetime import timedelta + +from asynctest import patch +import plexapi +import requests + +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +import homeassistant.components.plex.const as const +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_TOKEN, + CONF_VERIFY_SSL, +) +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN +from .mock_classes import MockPlexServer + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_setup_with_config(hass): + """Test setup component with config.""" + config = { + const.DOMAIN: { + CONF_HOST: MOCK_SERVERS[0][CONF_HOST], + CONF_PORT: MOCK_SERVERS[0][CONF_PORT], + CONF_TOKEN: MOCK_TOKEN, + CONF_SSL: True, + CONF_VERIFY_SSL: True, + MP_DOMAIN: { + const.CONF_IGNORE_NEW_SHARED_USERS: False, + const.CONF_USE_EPISODE_ART: False, + }, + }, + } + + mock_plex_server = MockPlexServer() + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + assert await async_setup_component(hass, const.DOMAIN, config) is True + await hass.async_block_till_done() + + assert mock_listen.called + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + entry = hass.config_entries.async_entries(const.DOMAIN)[0] + assert entry.state == ENTRY_STATE_LOADED + + server_id = mock_plex_server.machineIdentifier + loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] + + assert loaded_server.plex_server == mock_plex_server + + assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS] + assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS] + assert ( + hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS + ) + + +async def test_setup_with_config_entry(hass): + """Test setup component with config.""" + + mock_plex_server = MockPlexServer() + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert mock_listen.called + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + server_id = mock_plex_server.machineIdentifier + loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] + + assert loaded_server.plex_server == mock_plex_server + + assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS] + assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS] + assert ( + hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS + ) + + async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(mock_plex_server.accounts)) + + async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + with patch.object( + mock_plex_server, "clients", side_effect=plexapi.exceptions.BadRequest + ): + async_dispatcher_send( + hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) + ) + await hass.async_block_till_done() + + with patch.object( + mock_plex_server, "clients", side_effect=requests.exceptions.RequestException + ): + async_dispatcher_send( + hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) + ) + await hass.async_block_till_done() + + +async def test_set_config_entry_unique_id(hass): + """Test updating missing unique_id from config entry.""" + + mock_plex_server = MockPlexServer() + + entry = MockConfigEntry( + domain=const.DOMAIN, data=DEFAULT_DATA, options=DEFAULT_OPTIONS, unique_id=None, + ) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert mock_listen.called + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + assert ( + hass.config_entries.async_entries(const.DOMAIN)[0].unique_id + == mock_plex_server.machineIdentifier + ) + + +async def test_setup_config_entry_with_error(hass): + """Test setup component from config entry with errors.""" + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + with patch( + "homeassistant.components.plex.PlexServer.connect", + side_effect=requests.exceptions.ConnectionError, + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) is False + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_SETUP_RETRY + + with patch( + "homeassistant.components.plex.PlexServer.connect", + side_effect=plexapi.exceptions.BadRequest, + ): + next_update = dt_util.utcnow() + timedelta(seconds=30) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_SETUP_ERROR + + +async def test_setup_with_insecure_config_entry(hass): + """Test setup component with config.""" + + mock_plex_server = MockPlexServer() + + INSECURE_DATA = copy.deepcopy(DEFAULT_DATA) + INSECURE_DATA[const.PLEX_SERVER_CONFIG][CONF_VERIFY_SSL] = False + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=INSECURE_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert mock_listen.called + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + +async def test_unload_config_entry(hass): + """Test unloading a config entry.""" + mock_plex_server = MockPlexServer() + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + entry.add_to_hass(hass) + + config_entries = hass.config_entries.async_entries(const.DOMAIN) + assert len(config_entries) == 1 + assert entry is config_entries[0] + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ) as mock_listen: + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert mock_listen.called + + assert entry.state == ENTRY_STATE_LOADED + + server_id = mock_plex_server.machineIdentifier + loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] + + assert loaded_server.plex_server == mock_plex_server + + assert server_id in hass.data[const.DOMAIN][const.DISPATCHERS] + assert server_id in hass.data[const.DOMAIN][const.WEBSOCKETS] + assert ( + hass.data[const.DOMAIN][const.PLATFORMS_COMPLETED][server_id] == const.PLATFORMS + ) + + with patch("homeassistant.components.plex.PlexWebsocket.close") as mock_close: + await hass.config_entries.async_unload(entry.entry_id) + assert mock_close.called + + assert entry.state == ENTRY_STATE_NOT_LOADED + + assert server_id not in hass.data[const.DOMAIN][const.SERVERS] + assert server_id not in hass.data[const.DOMAIN][const.DISPATCHERS] + assert server_id not in hass.data[const.DOMAIN][const.WEBSOCKETS] + + +async def test_setup_with_photo_session(hass): + """Test setup component with config.""" + + mock_plex_server = MockPlexServer(session_type="photo") + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + media_player = hass.states.get("media_player.plex_product_title") + assert media_player.state == "idle" + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(mock_plex_server.accounts)) diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py new file mode 100644 index 00000000000..646a6ded32e --- /dev/null +++ b/tests/components/plex/test_server.py @@ -0,0 +1,134 @@ +"""Tests for Plex server.""" +import copy + +from asynctest import patch + +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.plex.const import ( + CONF_IGNORE_NEW_SHARED_USERS, + CONF_MONITORED_USERS, + DOMAIN, + PLEX_UPDATE_PLATFORMS_SIGNAL, + SERVERS, +) +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .const import DEFAULT_DATA, DEFAULT_OPTIONS +from .mock_classes import MockPlexServer + +from tests.common import MockConfigEntry + + +async def test_new_users_available(hass): + """Test setting up when new users available on Plex server.""" + + MONITORED_USERS = {"Owner": {"enabled": True}} + OPTIONS_WITH_USERS = copy.deepcopy(DEFAULT_OPTIONS) + OPTIONS_WITH_USERS[MP_DOMAIN][CONF_MONITORED_USERS] = MONITORED_USERS + + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=OPTIONS_WITH_USERS, + unique_id=DEFAULT_DATA["server_id"], + ) + + mock_plex_server = MockPlexServer(config_entry=entry) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users + + ignored_users = [x for x in monitored_users if not monitored_users[x]["enabled"]] + assert len(monitored_users) == 1 + assert len(ignored_users) == 0 + + 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): + """Test setting up when new users available on Plex server but are ignored.""" + + MONITORED_USERS = {"Owner": {"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 + + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=OPTIONS_WITH_USERS, + unique_id=DEFAULT_DATA["server_id"], + ) + + mock_plex_server = MockPlexServer(config_entry=entry) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + 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: + assert f"Ignoring Plex client owned by {ignored_user}" in caplog.text + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(mock_plex_server.accounts)) + + +async def test_mark_sessions_idle(hass): + """Test marking media_players as idle when sessions end.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + mock_plex_server = MockPlexServer(config_entry=entry) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "homeassistant.components.plex.PlexWebsocket.listen" + ): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == str(len(mock_plex_server.accounts)) + + mock_plex_server.clear_clients() + mock_plex_server.clear_sessions() + + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + + sensor = hass.states.get("sensor.plex_plex_server_1") + assert sensor.state == "0"