From e8982978060bb035de6cb3457b4ef960427a3679 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Mon, 9 Sep 2019 22:53:30 -0500 Subject: [PATCH 01/39] Add config flow support --- homeassistant/components/plex/__init__.py | 171 +++++--------- homeassistant/components/plex/config_flow.py | 212 ++++++++++++++++++ homeassistant/components/plex/const.py | 1 + homeassistant/components/plex/errors.py | 14 ++ homeassistant/components/plex/manifest.json | 3 +- homeassistant/components/plex/media_player.py | 28 ++- homeassistant/components/plex/sensor.py | 31 ++- homeassistant/components/plex/server.py | 7 + homeassistant/components/plex/strings.json | 45 ++++ homeassistant/generated/config_flows.py | 1 + 10 files changed, 388 insertions(+), 125 deletions(-) create mode 100644 homeassistant/components/plex/config_flow.py create mode 100644 homeassistant/components/plex/errors.py create mode 100644 homeassistant/components/plex/strings.json diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 69e77c8854fef1..20cf8ba37d2cd8 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -5,6 +5,7 @@ import requests.exceptions import voluptuous as vol +from homeassistant import config_entries from homeassistant.components.discovery import SERVICE_PLEX from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.const import ( @@ -17,12 +18,13 @@ ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery -from homeassistant.util.json import load_json, save_json +from homeassistant.util.json import load_json from .const import ( - CONF_SERVER, CONF_USE_EPISODE_ART, CONF_SHOW_ALL_CONTROLS, + CONF_SERVER, + CONF_SERVER_IDENTIFIER, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL, @@ -30,6 +32,7 @@ PLATFORMS, PLEX_CONFIG_FILE, PLEX_MEDIA_PLAYER_OPTIONS, + PLEX_SERVER_CONFIG, SERVERS, ) from .server import PlexServer @@ -58,7 +61,6 @@ CONFIG_SCHEMA = vol.Schema({PLEX_DOMAIN: SERVER_CONFIG_SCHEMA}, extra=vol.ALLOW_EXTRA) -CONFIGURING = "configuring" _LOGGER = logging.getLogger(__package__) @@ -66,52 +68,71 @@ def setup(hass, config): """Set up the Plex component.""" def server_discovered(service, info): - """Pass back discovered Plex server details.""" - if hass.data[PLEX_DOMAIN][SERVERS]: + """Pass discovered Plex server details to a config flow.""" + if hass.config_entries.async_entries(PLEX_DOMAIN): _LOGGER.debug("Plex server already configured, ignoring discovery.") return _LOGGER.debug("Discovered Plex server: %s:%s", info["host"], info["port"]) - setup_plex(discovery_info=info) + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": config_entries.SOURCE_DISCOVERY}, + data=info, + ) + ) - def setup_plex(config=None, discovery_info=None, configurator_info=None): - """Return assembled server_config dict.""" + def setup_plex(config): + """Pass configuration to a config flow.""" json_file = hass.config.path(PLEX_CONFIG_FILE) file_config = load_json(json_file) - host_and_port = None if config: - server_config = config - if CONF_HOST in server_config: - host_and_port = ( - f"{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" + if MP_DOMAIN in config: + hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = config.pop(MP_DOMAIN) + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config, ) - if MP_DOMAIN in server_config: - hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) + ) elif file_config: - _LOGGER.debug("Loading config from %s", json_file) - host_and_port, server_config = file_config.popitem() - server_config[CONF_VERIFY_SSL] = server_config.pop("verify") - elif discovery_info: - server_config = {} - host_and_port = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" - elif configurator_info: - server_config = configurator_info - host_and_port = server_config["host_and_port"] + if not hass.config_entries.async_entries(PLEX_DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": "import_plex_conf"}, + data=file_config, + ) + ) + else: + _LOGGER.info("Legacy config file can be removed: %s", json_file) else: discovery.listen(hass, SERVICE_PLEX, server_discovered) - return True - if host_and_port: - use_ssl = server_config.get(CONF_SSL, DEFAULT_SSL) - http_prefix = "https" if use_ssl else "http" - server_config[CONF_URL] = f"{http_prefix}://{host_and_port}" + hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) + + plex_config = config.get(PLEX_DOMAIN, {}) + setup_plex(config=plex_config) + + return True + + +async def async_setup_entry(hass, entry): + """Set up Plex from a config entry.""" + hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) + + server_config = entry.data[PLEX_SERVER_CONFIG] + server_id = entry.data[CONF_SERVER_IDENTIFIER] - plex_server = PlexServer(server_config) + if server_id not in hass.data[PLEX_DOMAIN][SERVERS]: + plex_server = await hass.async_add_executor_job(PlexServer, server_config) try: - plex_server.connect() + await hass.async_add_executor_job(plex_server.connect) except requests.exceptions.ConnectionError as error: _LOGGER.error( - "Plex server could not be reached, please verify host and port: [%s]", + "Plex server (%s) could not be reached: [%s]", + server_config[CONF_URL], error, ) return False @@ -121,88 +142,20 @@ def setup_plex(config=None, discovery_info=None, configurator_info=None): plexapi.exceptions.NotFound, ) as error: _LOGGER.error( - "Connection to Plex server failed, please verify token and SSL settings: [%s]", + "Login to %s failed, verify token and SSL settings: [%s]", + server_config[CONF_SERVER], error, ) - request_configuration(host_and_port) return False else: - hass.data[PLEX_DOMAIN][SERVERS][ - plex_server.machine_identifier - ] = plex_server - - if host_and_port in hass.data[PLEX_DOMAIN][CONFIGURING]: - request_id = hass.data[PLEX_DOMAIN][CONFIGURING].pop(host_and_port) - configurator = hass.components.configurator - configurator.request_done(request_id) - _LOGGER.debug("Discovery configuration done") - if configurator_info: - # Write plex.conf if created via discovery/configurator - save_json( - hass.config.path(PLEX_CONFIG_FILE), - { - host_and_port: { - CONF_TOKEN: server_config[CONF_TOKEN], - CONF_SSL: use_ssl, - "verify": server_config[CONF_VERIFY_SSL], - } - }, - ) - - if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS): - hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = MEDIA_PLAYER_SCHEMA({}) - - for platform in PLATFORMS: - hass.helpers.discovery.load_platform( - platform, PLEX_DOMAIN, {}, original_config - ) - - return True + hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server - def request_configuration(host_and_port): - """Request configuration steps from the user.""" - configurator = hass.components.configurator - if host_and_port in hass.data[PLEX_DOMAIN][CONFIGURING]: - configurator.notify_errors( - hass.data[PLEX_DOMAIN][CONFIGURING][host_and_port], - "Failed to register, please try again.", - ) - return + if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS): + hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = MEDIA_PLAYER_SCHEMA({}) - def plex_configuration_callback(data): - """Handle configuration changes.""" - config = { - "host_and_port": host_and_port, - CONF_TOKEN: data.get("token"), - CONF_SSL: cv.boolean(data.get("ssl")), - CONF_VERIFY_SSL: cv.boolean(data.get("verify_ssl")), - } - setup_plex(configurator_info=config) - - hass.data[PLEX_DOMAIN][CONFIGURING][ - host_and_port - ] = configurator.request_config( - "Plex Media Server", - plex_configuration_callback, - description="Enter the X-Plex-Token", - entity_picture="/static/images/logo_plex_mediaserver.png", - submit_caption="Confirm", - fields=[ - {"id": "token", "name": "X-Plex-Token", "type": ""}, - {"id": "ssl", "name": "Use SSL", "type": ""}, - {"id": "verify_ssl", "name": "Verify SSL", "type": ""}, - ], + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) ) - # End of inner functions. - - original_config = config - - hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}, CONFIGURING: {}}) - - if hass.data[PLEX_DOMAIN][SERVERS]: - _LOGGER.debug("Plex server already configured") - return False - - plex_config = config.get(PLEX_DOMAIN, {}) - return setup_plex(config=plex_config) + return True diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py new file mode 100644 index 00000000000000..948055af3a8118 --- /dev/null +++ b/homeassistant/components/plex/config_flow.py @@ -0,0 +1,212 @@ +"""Config flow for Plex.""" +import logging + +import plexapi.exceptions +import requests.exceptions +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_URL, + CONF_HOST, + CONF_PORT, + CONF_TOKEN, + CONF_SSL, + CONF_VERIFY_SSL, +) + +from .const import ( + CONF_SERVER, + CONF_SERVER_IDENTIFIER, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, + DOMAIN, + PLEX_SERVER_CONFIG, + SERVERS, +) +from .errors import NoServersFound, ServerNotSpecified +from .server import PlexServer + +_LOGGER = logging.getLogger(__package__) + + +@config_entries.HANDLERS.register(DOMAIN) +class PlexFlowHandler(config_entries.ConfigFlow): + """Handle a Plex config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize the Plex flow.""" + self.current_login = {} + self.discovery_info = {} + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + manual_setup = user_input.get("manual_setup") + if manual_setup is True: + return await self.async_step_manual_setup() + + self.current_login = user_input + + plex_server = await self.hass.async_add_executor_job(PlexServer, user_input) + try: + await self.hass.async_add_executor_job(plex_server.connect) + except NoServersFound: + errors["base"] = "no_servers" + except ServerNotSpecified as available_servers: + return self.async_show_form( + step_id="select_server", + data_schema=vol.Schema( + {vol.Required(CONF_SERVER): vol.In(available_servers.args[0])} + ), + errors={}, + ) + except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): + _LOGGER.error("Invalid credentials provided, config not created") + errors["base"] = "faulty_credentials" + except (plexapi.exceptions.NotFound, requests.exceptions.ConnectionError): + errors["base"] = "not_found" + except Exception as error: # pylint: disable=broad-except + _LOGGER.error("Unknown error connecting to Plex server: %s", error) + return self.async_abort(reason="unknown") + else: + server_id = plex_server.machine_identifier + + # Allow shared server creation from imports... + self.hass.data[DOMAIN][SERVERS][server_id] = plex_server + + # ...but do not create a new config entry if it already exists. + for entry in self._async_current_entries(): + if entry.data[CONF_SERVER_IDENTIFIER] == server_id: + return self.async_abort( # pylint: disable=lost-exception + reason="already_configured" + ) + + url = plex_server.url_in_use + token = user_input.get(CONF_TOKEN) + + server_config = {CONF_URL: url} + if token: + server_config[CONF_TOKEN] = token + if url.startswith("https"): + server_config[CONF_VERIFY_SSL] = user_input.get( + CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL + ) + + return self.async_create_entry( # pylint: disable=lost-exception + title=plex_server.friendly_name, + data={ + CONF_SERVER: plex_server.friendly_name, + CONF_SERVER_IDENTIFIER: server_id, + PLEX_SERVER_CONFIG: server_config, + }, + ) + + data_schema = vol.Schema( + { + vol.Optional( + CONF_TOKEN, default=self.current_login.get(CONF_TOKEN, "") + ): str, + vol.Optional("manual_setup"): bool, + } + ) + + return self.async_show_form( + step_id="user", data_schema=data_schema, errors=errors + ) + + async def async_step_manual_setup(self, user_input=None): + """Begin manual configuration.""" + if user_input is None: + data_schema = vol.Schema( + { + vol.Required( + CONF_HOST, default=self.discovery_info.get(CONF_HOST) + ): str, + vol.Required( + CONF_PORT, + default=int(self.discovery_info.get(CONF_PORT, DEFAULT_PORT)), + ): int, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool, + vol.Optional(CONF_TOKEN): str, + } + ) + return self.async_show_form(step_id="manual_setup", data_schema=data_schema) + + host = user_input.pop(CONF_HOST) + port = user_input.pop(CONF_PORT) + prefix = "https" if user_input.get(CONF_SSL) else "http" + user_input[CONF_URL] = f"{prefix}://{host}:{port}" + return await self.async_step_user(user_input=user_input) + + async def async_step_select_server(self, user_input=None): + """Use selected Plex server.""" + if user_input is None: + return await self.async_step_user() + + config = self.current_login + config[CONF_SERVER] = user_input.get(CONF_SERVER) + return await self.async_step_user(user_input=config) + + async def async_step_discovery(self, discovery_info): + """Set default host and port from discovery.""" + if self._async_in_progress(): + return self.async_abort(reason="already_in_progress") + + self.discovery_info = discovery_info + return await self.async_step_user() + + async def async_step_import_plex_conf(self, import_config): + """Import from Plex media_player file config. + + Legacy. + """ + if self._async_in_progress(): + return self.async_abort(reason="already_in_progress") + + host_and_port, host_config = import_config.popitem() + prefix = "https" if host_config[CONF_SSL] else "http" + url = f"{prefix}://{host_and_port}" + + config = { + CONF_URL: url, + CONF_TOKEN: host_config[CONF_TOKEN], + CONF_VERIFY_SSL: host_config["verify"], + } + + _LOGGER.info("Imported configuration from legacy config file") + return await self.async_step_user(user_input=config) + + async def async_step_import(self, import_config): + """Import from Plex configuration.""" + if self._async_in_progress(): + return self.async_abort(reason="already_in_progress") + + host = import_config.get(CONF_HOST) + port = import_config[CONF_PORT] + token = import_config.get(CONF_TOKEN) + server = import_config.get(CONF_SERVER) + + if host and port: + prefix = "https" if import_config[CONF_SSL] else "http" + url = f"{prefix}://{host}:{port}" + + config = { + CONF_URL: url, + CONF_TOKEN: token, + CONF_VERIFY_SSL: import_config[CONF_VERIFY_SSL], + } + elif token: + config = {CONF_TOKEN: token, CONF_SERVER: server} + else: + return self.async_abort(reason="invalid_import") + + _LOGGER.debug("Imported Plex configuration") + return await self.async_step_user(user_input=config) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 6f19623c809e86..e77ac303bf1a25 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -14,5 +14,6 @@ PLEX_SERVER_CONFIG = "server_config" CONF_SERVER = "server" +CONF_SERVER_IDENTIFIER = "server_id" CONF_USE_EPISODE_ART = "use_episode_art" CONF_SHOW_ALL_CONTROLS = "show_all_controls" diff --git a/homeassistant/components/plex/errors.py b/homeassistant/components/plex/errors.py new file mode 100644 index 00000000000000..11c15404f4505c --- /dev/null +++ b/homeassistant/components/plex/errors.py @@ -0,0 +1,14 @@ +"""Errors for the Plex component.""" +from homeassistant.exceptions import HomeAssistantError + + +class PlexException(HomeAssistantError): + """Base class for Plex exceptions.""" + + +class NoServersFound(PlexException): + """No servers found on Plex account.""" + + +class ServerNotSpecified(PlexException): + """Multiple servers linked to account without choice provided.""" diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 4269400dc2456e..94d990952a684e 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -1,11 +1,12 @@ { "domain": "plex", "name": "Plex", + "config_flow": true, "documentation": "https://www.home-assistant.io/components/plex", "requirements": [ "plexapi==3.0.6" ], - "dependencies": ["configurator"], + "dependencies": [], "codeowners": [ "@jjlawren" ] diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index cfc63948bee80c..814fbf98977b7f 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -35,26 +35,40 @@ from .const import ( CONF_USE_EPISODE_ART, CONF_SHOW_ALL_CONTROLS, + CONF_SERVER_IDENTIFIER, DOMAIN as PLEX_DOMAIN, NAME_FORMAT, PLEX_MEDIA_PLAYER_OPTIONS, SERVERS, ) -SERVER_SETUP = "server_setup" - -_CONFIGURING = {} _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities_callback, discovery_info=None): - """Set up the Plex platform.""" - if discovery_info is None: - return + """Set up the Plex media_player platform. + + Deprecated. + """ + return + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Plex media_player from a config entry.""" + + def add_entities(devices, update_before_add=False): + """Sync version of async add devices.""" + hass.add_job(async_add_entities, devices, update_before_add) + + hass.async_add_executor_job(_setup_platform, hass, config_entry, add_entities) + - plexserver = list(hass.data[PLEX_DOMAIN][SERVERS].values())[0] +def _setup_platform(hass, config_entry, add_entities_callback): + """Set up the Plex media_player platform.""" + server_id = config_entry.data[CONF_SERVER_IDENTIFIER] config = hass.data[PLEX_MEDIA_PLAYER_OPTIONS] + plexserver = hass.data[PLEX_DOMAIN][SERVERS][server_id] plex_clients = {} plex_sessions = {} track_time_interval(hass, lambda now: update_devices(), timedelta(seconds=10)) diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index f469e95da808e0..07edf015d0d590 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -8,21 +8,36 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -from .const import DOMAIN as PLEX_DOMAIN, SERVERS +from .const import CONF_SERVER_IDENTIFIER, DOMAIN as PLEX_DOMAIN, SERVERS -DEFAULT_NAME = "Plex" _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Plex sensor.""" - if discovery_info is None: - return + """Set up the Plex sensor platform. - plexserver = list(hass.data[PLEX_DOMAIN][SERVERS].values())[0] - add_entities([PlexSensor(plexserver)], True) + Deprecated. + """ + return + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Plex sensor from a config entry.""" + + def add_entities(devices, update_before_add=False): + """Sync version of async add devices.""" + hass.add_job(async_add_entities, devices, update_before_add) + + hass.async_add_executor_job(_setup_platform, hass, config_entry, add_entities) + + +def _setup_platform(hass, config_entry, add_entities): + """Set up the Plex sensor platform.""" + server_id = config_entry.data[CONF_SERVER_IDENTIFIER] + sensor = PlexSensor(hass.data[PLEX_DOMAIN][SERVERS][server_id]) + add_entities([sensor], True) class PlexSensor(Entity): @@ -30,10 +45,10 @@ class PlexSensor(Entity): def __init__(self, plex_server): """Initialize the sensor.""" - self._name = DEFAULT_NAME self._state = None self._now_playing = [] self._server = plex_server + self._name = f"Plex ({plex_server.friendly_name})" self._unique_id = f"sensor-{plex_server.machine_identifier}" @property diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 962e074996f5e5..58e4c0b0ae4be0 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -8,6 +8,7 @@ from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from .const import CONF_SERVER, DEFAULT_VERIFY_SSL +from .errors import NoServersFound, ServerNotSpecified _LOGGER = logging.getLogger(__package__) @@ -31,6 +32,12 @@ def _set_missing_url(): available_servers = [ x.name for x in account.resources() if "server" in x.provides ] + + if not available_servers: + raise NoServersFound + if not self._server_name and len(available_servers) > 1: + raise ServerNotSpecified(available_servers) + server_choice = ( self._server_name if self._server_name else available_servers[0] ) diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json new file mode 100644 index 00000000000000..d5fa8ffc83df0a --- /dev/null +++ b/homeassistant/components/plex/strings.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "Plex", + "step": { + "manual_setup": { + "title": "Plex server", + "data": { + "host": "Host", + "port": "Port", + "ssl": "Use SSL", + "verify_ssl": "Verify SSL certificate", + "token": "Token (if required)" + } + }, + "select_server": { + "title": "Select Plex server", + "description": "Multiple servers available, select one:", + "data": { + "server": "Server" + } + }, + "user": { + "title": "Connect Plex server", + "description": "Enter a Plex token for automatic setup or manually configure a server.", + "data": { + "token": "Plex token", + "manual_setup": "Manual setup" + } + } + }, + "error": { + "config_not_ready": "Configuration provided is incomplete", + "faulty_credentials": "Authorization failed", + "no_servers": "No servers linked to account", + "not_found": "Plex server not found", + "server_not_specified": "Multiple servers available, select one" + }, + "abort": { + "already_configured": "This Plex server is already configured", + "already_in_progress": "Plex is being configured", + "invalid_import": "Imported configuration is invalid", + "unknown": "Failed for unknown reason" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 7f3f5c1f20d23b..9ddae5acdb9941 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -45,6 +45,7 @@ "openuv", "owntracks", "plaato", + "plex", "point", "ps4", "rainmachine", From 09f8cc73a9edba15e6f87441d2c2e976dcec9729 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Mon, 9 Sep 2019 23:13:36 -0500 Subject: [PATCH 02/39] Log error on failed connection --- homeassistant/components/plex/config_flow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 948055af3a8118..60c2cb76cd7652 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -71,6 +71,9 @@ async def async_step_user(self, user_input=None): _LOGGER.error("Invalid credentials provided, config not created") errors["base"] = "faulty_credentials" except (plexapi.exceptions.NotFound, requests.exceptions.ConnectionError): + _LOGGER.error( + "Plex server could not be reached: %s", user_input[CONF_URL] + ) errors["base"] = "not_found" except Exception as error: # pylint: disable=broad-except _LOGGER.error("Unknown error connecting to Plex server: %s", error) From 0d86d6722248d0de5f41b774398e9afe0443dbd0 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 06:41:32 -0500 Subject: [PATCH 03/39] Review comments --- homeassistant/components/plex/__init__.py | 6 +++--- homeassistant/components/plex/config_flow.py | 5 ++--- homeassistant/components/plex/media_player.py | 10 +++++----- homeassistant/components/plex/sensor.py | 16 +++------------- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 20cf8ba37d2cd8..1ce8cdbaa01037 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -126,7 +126,7 @@ async def async_setup_entry(hass, entry): server_id = entry.data[CONF_SERVER_IDENTIFIER] if server_id not in hass.data[PLEX_DOMAIN][SERVERS]: - plex_server = await hass.async_add_executor_job(PlexServer, server_config) + plex_server = PlexServer(server_config) try: await hass.async_add_executor_job(plex_server.connect) except requests.exceptions.ConnectionError as error: @@ -147,8 +147,8 @@ async def async_setup_entry(hass, entry): error, ) return False - else: - hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server + + hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS): hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = MEDIA_PLAYER_SCHEMA({}) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 60c2cb76cd7652..291d2bf63212c3 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -31,8 +31,7 @@ _LOGGER = logging.getLogger(__package__) -@config_entries.HANDLERS.register(DOMAIN) -class PlexFlowHandler(config_entries.ConfigFlow): +class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Plex config flow.""" VERSION = 1 @@ -54,7 +53,7 @@ async def async_step_user(self, user_input=None): self.current_login = user_input - plex_server = await self.hass.async_add_executor_job(PlexServer, user_input) + plex_server = PlexServer(user_input) try: await self.hass.async_add_executor_job(plex_server.connect) except NoServersFound: diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 814fbf98977b7f..87cfd1df9034c9 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -45,20 +45,20 @@ _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities_callback, discovery_info=None): +def async_setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the Plex media_player platform. Deprecated. """ - return + pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Plex media_player from a config entry.""" - def add_entities(devices, update_before_add=False): - """Sync version of async add devices.""" - hass.add_job(async_add_entities, devices, update_before_add) + def add_entities(entities, update_before_add=False): + """Sync version of async add entities.""" + hass.add_job(async_add_entities, entities, update_before_add) hass.async_add_executor_job(_setup_platform, hass, config_entry, add_entities) diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 07edf015d0d590..430c46262f5309 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -15,29 +15,19 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) -def setup_platform(hass, config, add_entities, discovery_info=None): +def async_setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Plex sensor platform. Deprecated. """ - return + pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Plex sensor from a config entry.""" - - def add_entities(devices, update_before_add=False): - """Sync version of async add devices.""" - hass.add_job(async_add_entities, devices, update_before_add) - - hass.async_add_executor_job(_setup_platform, hass, config_entry, add_entities) - - -def _setup_platform(hass, config_entry, add_entities): - """Set up the Plex sensor platform.""" server_id = config_entry.data[CONF_SERVER_IDENTIFIER] sensor = PlexSensor(hass.data[PLEX_DOMAIN][SERVERS][server_id]) - add_entities([sensor], True) + async_add_entities([sensor], True) class PlexSensor(Entity): From 1513c6ee36662f20b1a5f56f441b9702d5293507 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 07:50:35 -0500 Subject: [PATCH 04/39] Unused errors --- homeassistant/components/plex/strings.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index d5fa8ffc83df0a..398d2b666ea526 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -29,11 +29,9 @@ } }, "error": { - "config_not_ready": "Configuration provided is incomplete", "faulty_credentials": "Authorization failed", "no_servers": "No servers linked to account", - "not_found": "Plex server not found", - "server_not_specified": "Multiple servers available, select one" + "not_found": "Plex server not found" }, "abort": { "already_configured": "This Plex server is already configured", From 28922e5203cf05e1f7e02a7ff89b9f4ed35723fd Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 08:37:08 -0500 Subject: [PATCH 05/39] Move form to step --- homeassistant/components/plex/config_flow.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 291d2bf63212c3..6778d921156457 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -59,13 +59,7 @@ async def async_step_user(self, user_input=None): except NoServersFound: errors["base"] = "no_servers" except ServerNotSpecified as available_servers: - return self.async_show_form( - step_id="select_server", - data_schema=vol.Schema( - {vol.Required(CONF_SERVER): vol.In(available_servers.args[0])} - ), - errors={}, - ) + return await self.async_step_select_server(available_servers.args[0]) except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): _LOGGER.error("Invalid credentials provided, config not created") errors["base"] = "faulty_credentials" @@ -153,6 +147,13 @@ async def async_step_select_server(self, user_input=None): if user_input is None: return await self.async_step_user() + if isinstance(user_input, list): + return self.async_show_form( + step_id="select_server", + data_schema=vol.Schema({vol.Required(CONF_SERVER): vol.In(user_input)}), + errors={}, + ) + config = self.current_login config[CONF_SERVER] = user_input.get(CONF_SERVER) return await self.async_step_user(user_input=config) From 63d67524326b233f4b09dc0a28c94d07ae3068f7 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 09:50:39 -0500 Subject: [PATCH 06/39] Use instance var instead of passing argument --- homeassistant/components/plex/config_flow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 6778d921156457..da1d69da0f3844 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -41,6 +41,7 @@ def __init__(self): """Initialize the Plex flow.""" self.current_login = {} self.discovery_info = {} + self.available_servers = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -59,7 +60,8 @@ async def async_step_user(self, user_input=None): except NoServersFound: errors["base"] = "no_servers" except ServerNotSpecified as available_servers: - return await self.async_step_select_server(available_servers.args[0]) + self.available_servers = available_servers.args[0] + return await self.async_step_select_server() except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): _LOGGER.error("Invalid credentials provided, config not created") errors["base"] = "faulty_credentials" @@ -145,12 +147,11 @@ async def async_step_manual_setup(self, user_input=None): async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" if user_input is None: - return await self.async_step_user() - - if isinstance(user_input, list): return self.async_show_form( step_id="select_server", - data_schema=vol.Schema({vol.Required(CONF_SERVER): vol.In(user_input)}), + data_schema=vol.Schema( + {vol.Required(CONF_SERVER): vol.In(self.available_servers)} + ), errors={}, ) From 4384f53b215efbbd9d667e75268170a813dfa0a2 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 10:15:37 -0500 Subject: [PATCH 07/39] Only share servers created by component --- homeassistant/components/plex/__init__.py | 49 +++++++++----------- homeassistant/components/plex/config_flow.py | 6 --- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 1ce8cdbaa01037..549fd6adf2904a 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -24,7 +24,6 @@ CONF_USE_EPISODE_ART, CONF_SHOW_ALL_CONTROLS, CONF_SERVER, - CONF_SERVER_IDENTIFIER, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL, @@ -123,32 +122,30 @@ async def async_setup_entry(hass, entry): hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) server_config = entry.data[PLEX_SERVER_CONFIG] - server_id = entry.data[CONF_SERVER_IDENTIFIER] - - if server_id not in hass.data[PLEX_DOMAIN][SERVERS]: - plex_server = PlexServer(server_config) - try: - await hass.async_add_executor_job(plex_server.connect) - except requests.exceptions.ConnectionError as error: - _LOGGER.error( - "Plex server (%s) could not be reached: [%s]", - server_config[CONF_URL], - error, - ) - return False - except ( - plexapi.exceptions.BadRequest, - plexapi.exceptions.Unauthorized, - plexapi.exceptions.NotFound, - ) as error: - _LOGGER.error( - "Login to %s failed, verify token and SSL settings: [%s]", - server_config[CONF_SERVER], - error, - ) - return False - hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server + plex_server = PlexServer(server_config) + try: + await hass.async_add_executor_job(plex_server.connect) + except requests.exceptions.ConnectionError as error: + _LOGGER.error( + "Plex server (%s) could not be reached: [%s]", + server_config[CONF_URL], + error, + ) + return False + except ( + plexapi.exceptions.BadRequest, + plexapi.exceptions.Unauthorized, + plexapi.exceptions.NotFound, + ) as error: + _LOGGER.error( + "Login to %s failed, verify token and SSL settings: [%s]", + server_config[CONF_SERVER], + error, + ) + return False + + hass.data[PLEX_DOMAIN][SERVERS][plex_server.machine_identifier] = plex_server if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS): hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = MEDIA_PLAYER_SCHEMA({}) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index da1d69da0f3844..4a7f75109e8453 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -23,7 +23,6 @@ DEFAULT_VERIFY_SSL, DOMAIN, PLEX_SERVER_CONFIG, - SERVERS, ) from .errors import NoServersFound, ServerNotSpecified from .server import PlexServer @@ -75,11 +74,6 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="unknown") else: server_id = plex_server.machine_identifier - - # Allow shared server creation from imports... - self.hass.data[DOMAIN][SERVERS][server_id] = plex_server - - # ...but do not create a new config entry if it already exists. for entry in self._async_current_entries(): if entry.data[CONF_SERVER_IDENTIFIER] == server_id: return self.async_abort( # pylint: disable=lost-exception From efe1d7cc203a62cf3982c253f854936fd8e619c1 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 10:19:48 -0500 Subject: [PATCH 08/39] Return errors early to avoid try:else --- homeassistant/components/plex/config_flow.py | 66 +++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 4a7f75109e8453..832822ca4ac22e 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -46,6 +46,15 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} + data_schema = vol.Schema( + { + vol.Optional( + CONF_TOKEN, default=self.current_login.get(CONF_TOKEN, "") + ): str, + vol.Optional("manual_setup"): bool, + } + ) + if user_input is not None: manual_setup = user_input.get("manual_setup") if manual_setup is True: @@ -73,41 +82,36 @@ async def async_step_user(self, user_input=None): _LOGGER.error("Unknown error connecting to Plex server: %s", error) return self.async_abort(reason="unknown") else: - server_id = plex_server.machine_identifier - for entry in self._async_current_entries(): - if entry.data[CONF_SERVER_IDENTIFIER] == server_id: - return self.async_abort( # pylint: disable=lost-exception - reason="already_configured" - ) - - url = plex_server.url_in_use - token = user_input.get(CONF_TOKEN) - - server_config = {CONF_URL: url} - if token: - server_config[CONF_TOKEN] = token - if url.startswith("https"): - server_config[CONF_VERIFY_SSL] = user_input.get( - CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL + if errors: + return self.async_show_form( + step_id="user", data_schema=data_schema, errors=errors ) - return self.async_create_entry( # pylint: disable=lost-exception - title=plex_server.friendly_name, - data={ - CONF_SERVER: plex_server.friendly_name, - CONF_SERVER_IDENTIFIER: server_id, - PLEX_SERVER_CONFIG: server_config, - }, + server_id = plex_server.machine_identifier + + for entry in self._async_current_entries(): + if entry.data[CONF_SERVER_IDENTIFIER] == server_id: + return self.async_abort(reason="already_configured") + + url = plex_server.url_in_use + token = user_input.get(CONF_TOKEN) + + server_config = {CONF_URL: url} + if token: + server_config[CONF_TOKEN] = token + if url.startswith("https"): + server_config[CONF_VERIFY_SSL] = user_input.get( + CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL ) - data_schema = vol.Schema( - { - vol.Optional( - CONF_TOKEN, default=self.current_login.get(CONF_TOKEN, "") - ): str, - vol.Optional("manual_setup"): bool, - } - ) + return self.async_create_entry( + title=plex_server.friendly_name, + data={ + CONF_SERVER: plex_server.friendly_name, + CONF_SERVER_IDENTIFIER: server_id, + PLEX_SERVER_CONFIG: server_config, + }, + ) return self.async_show_form( step_id="user", data_schema=data_schema, errors=errors From 97b7fc2e87120c779dab664daabbe5851a161b81 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 10:20:53 -0500 Subject: [PATCH 09/39] Separate debug for validation vs setup --- homeassistant/components/plex/__init__.py | 3 +++ homeassistant/components/plex/config_flow.py | 2 ++ homeassistant/components/plex/server.py | 5 ----- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 549fd6adf2904a..49bf445fe575b0 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -145,6 +145,9 @@ async def async_setup_entry(hass, entry): ) return False + _LOGGER.debug( + "Connected to: %s (%s)", plex_server.friendly_name, plex_server.url_in_use + ) hass.data[PLEX_DOMAIN][SERVERS][plex_server.machine_identifier] = plex_server if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS): diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 832822ca4ac22e..6fdc75d791eae1 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -104,6 +104,8 @@ async def async_step_user(self, user_input=None): CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL ) + _LOGGER.debug("Valid config created for %s", plex_server.friendly_name) + return self.async_create_entry( title=plex_server.friendly_name, data={ diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 58e4c0b0ae4be0..f8fb11a1f2b3e7 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -1,6 +1,4 @@ """Shared class to maintain Plex server instances.""" -import logging - import plexapi.myplex import plexapi.server from requests import Session @@ -10,8 +8,6 @@ from .const import CONF_SERVER, DEFAULT_VERIFY_SSL from .errors import NoServersFound, ServerNotSpecified -_LOGGER = logging.getLogger(__package__) - class PlexServer: """Manages a single Plex server connection.""" @@ -54,7 +50,6 @@ def _connect_with_url(): self._plex_server = plexapi.server.PlexServer( self._url, self._token, session ) - _LOGGER.debug("Connected to: %s (%s)", self.friendly_name, self.url_in_use) if self._token and not self._url: _set_missing_url() From 84bfc3cd4bb0b66a7146d1969a300c827dfa8226 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 10:30:38 -0500 Subject: [PATCH 10/39] Unnecessary --- homeassistant/components/plex/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 49bf445fe575b0..e78a07f462bde8 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -119,8 +119,6 @@ def setup_plex(config): async def async_setup_entry(hass, entry): """Set up Plex from a config entry.""" - hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) - server_config = entry.data[PLEX_SERVER_CONFIG] plex_server = PlexServer(server_config) From 137afd0c8c3cb8502e3cdfe7157decd60f404930 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 10:42:46 -0500 Subject: [PATCH 11/39] Unnecessary checks --- homeassistant/components/plex/config_flow.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 6fdc75d791eae1..372ed5b5ba984b 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -172,9 +172,6 @@ async def async_step_import_plex_conf(self, import_config): Legacy. """ - if self._async_in_progress(): - return self.async_abort(reason="already_in_progress") - host_and_port, host_config = import_config.popitem() prefix = "https" if host_config[CONF_SSL] else "http" url = f"{prefix}://{host_and_port}" @@ -190,9 +187,6 @@ async def async_step_import_plex_conf(self, import_config): async def async_step_import(self, import_config): """Import from Plex configuration.""" - if self._async_in_progress(): - return self.async_abort(reason="already_in_progress") - host = import_config.get(CONF_HOST) port = import_config[CONF_PORT] token = import_config.get(CONF_TOKEN) From 9011d469646e0030f4492517f660c00a7b34d5bc Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 16:09:17 -0500 Subject: [PATCH 12/39] Combine import flows, move logic to component --- homeassistant/components/plex/__init__.py | 42 ++++++++++++-------- homeassistant/components/plex/config_flow.py | 26 +----------- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index e78a07f462bde8..eae318a28b8fe0 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -84,31 +84,41 @@ def setup_plex(config): """Pass configuration to a config flow.""" json_file = hass.config.path(PLEX_CONFIG_FILE) file_config = load_json(json_file) + server_config = None if config: - if MP_DOMAIN in config: - hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = config.pop(MP_DOMAIN) - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=config, - ) - ) + server_config = dict(config) + if MP_DOMAIN in server_config: + hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) + if CONF_HOST in server_config: + prefix = "https" if server_config.pop(CONF_SSL) else "http" + server_config[ + CONF_URL + ] = f"{prefix}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" elif file_config: if not hass.config_entries.async_entries(PLEX_DOMAIN): - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": "import_plex_conf"}, - data=file_config, - ) - ) + host_and_port, host_config = file_config.popitem() + prefix = "https" if host_config[CONF_SSL] else "http" + + server_config = { + CONF_URL: f"{prefix}://{host_and_port}", + CONF_TOKEN: host_config[CONF_TOKEN], + CONF_VERIFY_SSL: host_config["verify"], + } else: _LOGGER.info("Legacy config file can be removed: %s", json_file) else: discovery.listen(hass, SERVICE_PLEX, server_discovered) + if server_config: + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=server_config, + ) + ) + hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) plex_config = config.get(PLEX_DOMAIN, {}) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 372ed5b5ba984b..ef12f8a8c4d283 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -167,35 +167,13 @@ async def async_step_discovery(self, discovery_info): self.discovery_info = discovery_info return await self.async_step_user() - async def async_step_import_plex_conf(self, import_config): - """Import from Plex media_player file config. - - Legacy. - """ - host_and_port, host_config = import_config.popitem() - prefix = "https" if host_config[CONF_SSL] else "http" - url = f"{prefix}://{host_and_port}" - - config = { - CONF_URL: url, - CONF_TOKEN: host_config[CONF_TOKEN], - CONF_VERIFY_SSL: host_config["verify"], - } - - _LOGGER.info("Imported configuration from legacy config file") - return await self.async_step_user(user_input=config) - async def async_step_import(self, import_config): """Import from Plex configuration.""" - host = import_config.get(CONF_HOST) - port = import_config[CONF_PORT] + url = import_config.get(CONF_URL) token = import_config.get(CONF_TOKEN) server = import_config.get(CONF_SERVER) - if host and port: - prefix = "https" if import_config[CONF_SSL] else "http" - url = f"{prefix}://{host}:{port}" - + if url: config = { CONF_URL: url, CONF_TOKEN: token, From cbcdc2a765d9bad502a7c0a189bb35f165abe936 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 16:33:02 -0500 Subject: [PATCH 13/39] Use config entry discovery handler --- homeassistant/components/discovery/__init__.py | 2 +- homeassistant/components/plex/__init__.py | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 827e05a424be85..15fcfc15338da2 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -50,6 +50,7 @@ SERVICE_DAIKIN: "daikin", SERVICE_TELLDUSLIVE: "tellduslive", SERVICE_IGD: "upnp", + SERVICE_PLEX: "plex", } SERVICE_HANDLERS = { @@ -69,7 +70,6 @@ SERVICE_FREEBOX: ("freebox", None), SERVICE_YEELIGHT: ("yeelight", None), "panasonic_viera": ("media_player", "panasonic_viera"), - SERVICE_PLEX: ("plex", None), "yamaha": ("media_player", "yamaha"), "logitech_mediaserver": ("media_player", "squeezebox"), "directv": ("media_player", "directv"), diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index eae318a28b8fe0..e445d7d70e0f05 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -6,7 +6,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.discovery import SERVICE_PLEX from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.const import ( CONF_HOST, @@ -17,7 +16,6 @@ CONF_VERIFY_SSL, ) from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import discovery from homeassistant.util.json import load_json from .const import ( @@ -66,20 +64,6 @@ def setup(hass, config): """Set up the Plex component.""" - def server_discovered(service, info): - """Pass discovered Plex server details to a config flow.""" - if hass.config_entries.async_entries(PLEX_DOMAIN): - _LOGGER.debug("Plex server already configured, ignoring discovery.") - return - _LOGGER.debug("Discovered Plex server: %s:%s", info["host"], info["port"]) - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": config_entries.SOURCE_DISCOVERY}, - data=info, - ) - ) - def setup_plex(config): """Pass configuration to a config flow.""" json_file = hass.config.path(PLEX_CONFIG_FILE) @@ -107,8 +91,6 @@ def setup_plex(config): } else: _LOGGER.info("Legacy config file can be removed: %s", json_file) - else: - discovery.listen(hass, SERVICE_PLEX, server_discovered) if server_config: hass.async_create_task( From 828aa6d51d41d6ee0406d826c0fa464780edc26b Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 16:39:37 -0500 Subject: [PATCH 14/39] Temporary lint fix --- homeassistant/components/plex/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index ef12f8a8c4d283..0e04038f040ca6 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -15,7 +15,7 @@ CONF_VERIFY_SSL, ) -from .const import ( +from .const import ( # pylint: disable=unused-import CONF_SERVER, CONF_SERVER_IDENTIFIER, DEFAULT_PORT, From 304667ad802ac3ea4ca6829a85dbc9ae22cc958c Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 21:48:49 -0500 Subject: [PATCH 15/39] Filter out servers already configured --- homeassistant/components/plex/config_flow.py | 30 ++++++++++++++------ homeassistant/components/plex/server.py | 4 ++- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 0e04038f040ca6..684cf01f893339 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -146,17 +146,29 @@ async def async_step_manual_setup(self, user_input=None): async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" + config = dict(self.current_login) + if user_input is None: - return self.async_show_form( - step_id="select_server", - data_schema=vol.Schema( - {vol.Required(CONF_SERVER): vol.In(self.available_servers)} - ), - errors={}, - ) + configured_servers = [ + x.data[CONF_SERVER_IDENTIFIER] for x in self._async_current_entries() + ] + available_servers = [ + name + for (name, server_id) in self.available_servers + if server_id not in configured_servers + ] + if len(available_servers) > 1: + return self.async_show_form( + step_id="select_server", + data_schema=vol.Schema( + {vol.Required(CONF_SERVER): vol.In(available_servers)} + ), + errors={}, + ) + config[CONF_SERVER] = available_servers[0] + else: + config[CONF_SERVER] = user_input.get(CONF_SERVER) - config = self.current_login - config[CONF_SERVER] = user_input.get(CONF_SERVER) return await self.async_step_user(user_input=config) async def async_step_discovery(self, discovery_info): diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index f8fb11a1f2b3e7..f41a9bdabae183 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -26,7 +26,9 @@ def connect(self): def _set_missing_url(): account = plexapi.myplex.MyPlexAccount(token=self._token) available_servers = [ - x.name for x in account.resources() if "server" in x.provides + (x.name, x.clientIdentifier) + for x in account.resources() + if "server" in x.provides ] if not available_servers: From bb8e67e48c67b2abee8902a127c5a74399eb3b91 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 21:57:11 -0500 Subject: [PATCH 16/39] Remove manual config flow --- homeassistant/components/plex/config_flow.py | 43 +------------------- homeassistant/components/plex/strings.json | 15 +------ 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 684cf01f893339..376631c27e50fe 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -6,20 +6,11 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import ( - CONF_URL, - CONF_HOST, - CONF_PORT, - CONF_TOKEN, - CONF_SSL, - CONF_VERIFY_SSL, -) +from homeassistant.const import CONF_URL, CONF_TOKEN, CONF_VERIFY_SSL from .const import ( # pylint: disable=unused-import CONF_SERVER, CONF_SERVER_IDENTIFIER, - DEFAULT_PORT, - DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, PLEX_SERVER_CONFIG, @@ -50,16 +41,11 @@ async def async_step_user(self, user_input=None): { vol.Optional( CONF_TOKEN, default=self.current_login.get(CONF_TOKEN, "") - ): str, - vol.Optional("manual_setup"): bool, + ): str } ) if user_input is not None: - manual_setup = user_input.get("manual_setup") - if manual_setup is True: - return await self.async_step_manual_setup() - self.current_login = user_input plex_server = PlexServer(user_input) @@ -119,31 +105,6 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_manual_setup(self, user_input=None): - """Begin manual configuration.""" - if user_input is None: - data_schema = vol.Schema( - { - vol.Required( - CONF_HOST, default=self.discovery_info.get(CONF_HOST) - ): str, - vol.Required( - CONF_PORT, - default=int(self.discovery_info.get(CONF_PORT, DEFAULT_PORT)), - ): int, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool, - vol.Optional(CONF_TOKEN): str, - } - ) - return self.async_show_form(step_id="manual_setup", data_schema=data_schema) - - host = user_input.pop(CONF_HOST) - port = user_input.pop(CONF_PORT) - prefix = "https" if user_input.get(CONF_SSL) else "http" - user_input[CONF_URL] = f"{prefix}://{host}:{port}" - return await self.async_step_user(user_input=user_input) - async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" config = dict(self.current_login) diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 398d2b666ea526..551a2fdc3a3f64 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -2,16 +2,6 @@ "config": { "title": "Plex", "step": { - "manual_setup": { - "title": "Plex server", - "data": { - "host": "Host", - "port": "Port", - "ssl": "Use SSL", - "verify_ssl": "Verify SSL certificate", - "token": "Token (if required)" - } - }, "select_server": { "title": "Select Plex server", "description": "Multiple servers available, select one:", @@ -21,10 +11,9 @@ }, "user": { "title": "Connect Plex server", - "description": "Enter a Plex token for automatic setup or manually configure a server.", + "description": "Enter a Plex token for automatic setup.", "data": { - "token": "Plex token", - "manual_setup": "Manual setup" + "token": "Plex token" } } }, From b9813c972e1a353f891dfd117a1e13e646dd614b Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 22:37:32 -0500 Subject: [PATCH 17/39] Skip discovery if a config exists --- homeassistant/components/plex/config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 376631c27e50fe..d0f3b610c85440 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -134,8 +134,9 @@ async def async_step_select_server(self, user_input=None): async def async_step_discovery(self, discovery_info): """Set default host and port from discovery.""" - if self._async_in_progress(): - return self.async_abort(reason="already_in_progress") + if self._async_current_entries(): + # Skip discovery if any config already exists. + return self.async_abort(reason="already_configured") self.discovery_info = discovery_info return await self.async_step_user() From 220fb07b9fb2f5bfff77f56b389259cf74f42242 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 10 Sep 2019 22:43:37 -0500 Subject: [PATCH 18/39] Swap conditional to reduce indenting --- homeassistant/components/plex/config_flow.py | 104 +++++++++---------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index d0f3b610c85440..aebbbd602c2ad5 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -45,64 +45,62 @@ async def async_step_user(self, user_input=None): } ) - if user_input is not None: - self.current_login = user_input - - plex_server = PlexServer(user_input) - try: - await self.hass.async_add_executor_job(plex_server.connect) - except NoServersFound: - errors["base"] = "no_servers" - except ServerNotSpecified as available_servers: - self.available_servers = available_servers.args[0] - return await self.async_step_select_server() - except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): - _LOGGER.error("Invalid credentials provided, config not created") - errors["base"] = "faulty_credentials" - except (plexapi.exceptions.NotFound, requests.exceptions.ConnectionError): - _LOGGER.error( - "Plex server could not be reached: %s", user_input[CONF_URL] - ) - errors["base"] = "not_found" - except Exception as error: # pylint: disable=broad-except - _LOGGER.error("Unknown error connecting to Plex server: %s", error) - return self.async_abort(reason="unknown") - else: - if errors: - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors - ) - - server_id = plex_server.machine_identifier - - for entry in self._async_current_entries(): - if entry.data[CONF_SERVER_IDENTIFIER] == server_id: - return self.async_abort(reason="already_configured") - - url = plex_server.url_in_use - token = user_input.get(CONF_TOKEN) - - server_config = {CONF_URL: url} - if token: - server_config[CONF_TOKEN] = token - if url.startswith("https"): - server_config[CONF_VERIFY_SSL] = user_input.get( - CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=data_schema, errors=errors + ) + + self.current_login = user_input + + plex_server = PlexServer(user_input) + try: + await self.hass.async_add_executor_job(plex_server.connect) + except NoServersFound: + errors["base"] = "no_servers" + except ServerNotSpecified as available_servers: + self.available_servers = available_servers.args[0] + return await self.async_step_select_server() + except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): + _LOGGER.error("Invalid credentials provided, config not created") + errors["base"] = "faulty_credentials" + except (plexapi.exceptions.NotFound, requests.exceptions.ConnectionError): + _LOGGER.error("Plex server could not be reached: %s", user_input[CONF_URL]) + errors["base"] = "not_found" + except Exception as error: # pylint: disable=broad-except + _LOGGER.error("Unknown error connecting to Plex server: %s", error) + return self.async_abort(reason="unknown") + else: + if errors: + return self.async_show_form( + step_id="user", data_schema=data_schema, errors=errors ) - _LOGGER.debug("Valid config created for %s", plex_server.friendly_name) + server_id = plex_server.machine_identifier + + for entry in self._async_current_entries(): + if entry.data[CONF_SERVER_IDENTIFIER] == server_id: + return self.async_abort(reason="already_configured") - return self.async_create_entry( - title=plex_server.friendly_name, - data={ - CONF_SERVER: plex_server.friendly_name, - CONF_SERVER_IDENTIFIER: server_id, - PLEX_SERVER_CONFIG: server_config, - }, + url = plex_server.url_in_use + token = user_input.get(CONF_TOKEN) + + server_config = {CONF_URL: url} + if token: + server_config[CONF_TOKEN] = token + if url.startswith("https"): + server_config[CONF_VERIFY_SSL] = user_input.get( + CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL ) - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors + _LOGGER.debug("Valid config created for %s", plex_server.friendly_name) + + return self.async_create_entry( + title=plex_server.friendly_name, + data={ + CONF_SERVER: plex_server.friendly_name, + CONF_SERVER_IDENTIFIER: server_id, + PLEX_SERVER_CONFIG: server_config, + }, ) async def async_step_select_server(self, user_input=None): From 3a809ff6ce230fd5ba8e6b1c8a9ed8dd72e306fe Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 11 Sep 2019 15:50:15 -0500 Subject: [PATCH 19/39] Only discover when no configs created or creating --- homeassistant/components/plex/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index aebbbd602c2ad5..75ff47de3ae0ee 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -132,8 +132,8 @@ async def async_step_select_server(self, user_input=None): async def async_step_discovery(self, discovery_info): """Set default host and port from discovery.""" - if self._async_current_entries(): - # Skip discovery if any config already exists. + if self._async_current_entries() or self._async_in_progress(): + # Skip discovery if a config already exists or is in progress. return self.async_abort(reason="already_configured") self.discovery_info = discovery_info From 7592f8406fe2e7c9c2019d0c68c8000f3014630f Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 11 Sep 2019 15:53:01 -0500 Subject: [PATCH 20/39] Un-nest function --- homeassistant/components/plex/__init__.py | 78 +++++++++++------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index e445d7d70e0f05..ac88b0c734123b 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -63,52 +63,52 @@ def setup(hass, config): """Set up the Plex component.""" - - def setup_plex(config): - """Pass configuration to a config flow.""" - json_file = hass.config.path(PLEX_CONFIG_FILE) - file_config = load_json(json_file) - server_config = None - - if config: - server_config = dict(config) - if MP_DOMAIN in server_config: - hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) - if CONF_HOST in server_config: - prefix = "https" if server_config.pop(CONF_SSL) else "http" - server_config[ - CONF_URL - ] = f"{prefix}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" - elif file_config: - if not hass.config_entries.async_entries(PLEX_DOMAIN): - host_and_port, host_config = file_config.popitem() - prefix = "https" if host_config[CONF_SSL] else "http" - - server_config = { - CONF_URL: f"{prefix}://{host_and_port}", - CONF_TOKEN: host_config[CONF_TOKEN], - CONF_VERIFY_SSL: host_config["verify"], - } - else: - _LOGGER.info("Legacy config file can be removed: %s", json_file) - - if server_config: - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=server_config, - ) - ) - hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) plex_config = config.get(PLEX_DOMAIN, {}) - setup_plex(config=plex_config) + _setup_plex(hass, plex_config) return True +def _setup_plex(hass, config): + """Pass configuration to a config flow.""" + json_file = hass.config.path(PLEX_CONFIG_FILE) + file_config = load_json(json_file) + server_config = None + + if config: + server_config = dict(config) + if MP_DOMAIN in server_config: + hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) + if CONF_HOST in server_config: + prefix = "https" if server_config.pop(CONF_SSL) else "http" + server_config[ + CONF_URL + ] = f"{prefix}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" + elif file_config: + if not hass.config_entries.async_entries(PLEX_DOMAIN): + host_and_port, host_config = file_config.popitem() + prefix = "https" if host_config[CONF_SSL] else "http" + + server_config = { + CONF_URL: f"{prefix}://{host_and_port}", + CONF_TOKEN: host_config[CONF_TOKEN], + CONF_VERIFY_SSL: host_config["verify"], + } + else: + _LOGGER.info("Legacy config file can be removed: %s", json_file) + + if server_config: + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=server_config, + ) + ) + + async def async_setup_entry(hass, entry): """Set up Plex from a config entry.""" server_config = entry.data[PLEX_SERVER_CONFIG] From c5fdfa2c4112d2fb289077961e5a379a76b10a2c Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 11 Sep 2019 17:10:20 -0500 Subject: [PATCH 21/39] Proper async use --- homeassistant/components/plex/media_player.py | 2 +- homeassistant/components/plex/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 87cfd1df9034c9..bc19ff41dfedff 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -45,7 +45,7 @@ _LOGGER = logging.getLogger(__name__) -def async_setup_platform(hass, config, add_entities_callback, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Plex media_player platform. Deprecated. diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 430c46262f5309..7d5b54356a0c82 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -15,7 +15,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) -def async_setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Plex sensor platform. Deprecated. From da0d61dff14a28f25b826d30176dc494cc2a5420 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Thu, 12 Sep 2019 09:07:57 -0500 Subject: [PATCH 22/39] Move legacy file import to discovery --- homeassistant/components/plex/__init__.py | 52 ++++++-------------- homeassistant/components/plex/config_flow.py | 19 ++++++- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index ac88b0c734123b..665091d69b9b1b 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -16,7 +16,6 @@ CONF_VERIFY_SSL, ) from homeassistant.helpers import config_validation as cv -from homeassistant.util.json import load_json from .const import ( CONF_USE_EPISODE_ART, @@ -27,7 +26,6 @@ DEFAULT_VERIFY_SSL, DOMAIN as PLEX_DOMAIN, PLATFORMS, - PLEX_CONFIG_FILE, PLEX_MEDIA_PLAYER_OPTIONS, PLEX_SERVER_CONFIG, SERVERS, @@ -66,47 +64,29 @@ def setup(hass, config): hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}}) plex_config = config.get(PLEX_DOMAIN, {}) - _setup_plex(hass, plex_config) + if plex_config: + _setup_plex(hass, plex_config) return True def _setup_plex(hass, config): """Pass configuration to a config flow.""" - json_file = hass.config.path(PLEX_CONFIG_FILE) - file_config = load_json(json_file) - server_config = None - - if config: - server_config = dict(config) - if MP_DOMAIN in server_config: - hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) - if CONF_HOST in server_config: - prefix = "https" if server_config.pop(CONF_SSL) else "http" - server_config[ - CONF_URL - ] = f"{prefix}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" - elif file_config: - if not hass.config_entries.async_entries(PLEX_DOMAIN): - host_and_port, host_config = file_config.popitem() - prefix = "https" if host_config[CONF_SSL] else "http" - - server_config = { - CONF_URL: f"{prefix}://{host_and_port}", - CONF_TOKEN: host_config[CONF_TOKEN], - CONF_VERIFY_SSL: host_config["verify"], - } - else: - _LOGGER.info("Legacy config file can be removed: %s", json_file) - - if server_config: - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=server_config, - ) + server_config = dict(config) + if MP_DOMAIN in server_config: + hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN) + if CONF_HOST in server_config: + prefix = "https" if server_config.pop(CONF_SSL) else "http" + server_config[ + CONF_URL + ] = f"{prefix}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=server_config, ) + ) async def async_setup_entry(hass, entry): diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 75ff47de3ae0ee..3c1627b19431ba 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -6,13 +6,15 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_URL, CONF_TOKEN, CONF_VERIFY_SSL +from homeassistant.const import CONF_URL, CONF_TOKEN, CONF_SSL, CONF_VERIFY_SSL +from homeassistant.util.json import load_json from .const import ( # pylint: disable=unused-import CONF_SERVER, CONF_SERVER_IDENTIFIER, DEFAULT_VERIFY_SSL, DOMAIN, + PLEX_CONFIG_FILE, PLEX_SERVER_CONFIG, ) from .errors import NoServersFound, ServerNotSpecified @@ -136,6 +138,21 @@ async def async_step_discovery(self, discovery_info): # Skip discovery if a config already exists or is in progress. return self.async_abort(reason="already_configured") + json_file = self.hass.config.path(PLEX_CONFIG_FILE) + file_config = load_json(json_file) + + if file_config: + host_and_port, host_config = file_config.popitem() + prefix = "https" if host_config[CONF_SSL] else "http" + + server_config = { + CONF_URL: f"{prefix}://{host_and_port}", + CONF_TOKEN: host_config[CONF_TOKEN], + CONF_VERIFY_SSL: host_config["verify"], + } + _LOGGER.info("Imported legacy config, file can be removed: %s", json_file) + return await self.async_step_import(server_config) + self.discovery_info = discovery_info return await self.async_step_user() From 2694feadfe9b42e5aae8f3767dd979e4374982c4 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Thu, 12 Sep 2019 12:47:26 -0500 Subject: [PATCH 23/39] Fix, bad else --- homeassistant/components/plex/config_flow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 3c1627b19431ba..4b549c7ebb3127 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -71,11 +71,11 @@ async def async_step_user(self, user_input=None): except Exception as error: # pylint: disable=broad-except _LOGGER.error("Unknown error connecting to Plex server: %s", error) return self.async_abort(reason="unknown") - else: - if errors: - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors - ) + + if errors: + return self.async_show_form( + step_id="user", data_schema=self.USER_SCHEMA, errors=errors + ) server_id = plex_server.machine_identifier From 825d2dc7020ad238fd220f68648cc65ad4d1572e Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Thu, 12 Sep 2019 12:51:39 -0500 Subject: [PATCH 24/39] Separate validate step --- homeassistant/components/plex/config_flow.py | 63 +++++++------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 4b549c7ebb3127..2d110231b4203f 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -20,6 +20,8 @@ from .errors import NoServersFound, ServerNotSpecified from .server import PlexServer +USER_SCHEMA = vol.Schema({vol.Required(CONF_TOKEN): str}) + _LOGGER = logging.getLogger(__package__) @@ -37,24 +39,19 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - errors = {} - - data_schema = vol.Schema( - { - vol.Optional( - CONF_TOKEN, default=self.current_login.get(CONF_TOKEN, "") - ): str - } - ) - if user_input is None: return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors + step_id="user", data_schema=USER_SCHEMA, errors={} ) - self.current_login = user_input + return await self.async_step_server_validate(user_input) - plex_server = PlexServer(user_input) + async def async_step_server_validate(self, server_config): + """Validate a provided configuration.""" + errors = {} + self.current_login = server_config + + plex_server = PlexServer(server_config) try: await self.hass.async_add_executor_job(plex_server.connect) except NoServersFound: @@ -66,7 +63,9 @@ async def async_step_user(self, user_input=None): _LOGGER.error("Invalid credentials provided, config not created") errors["base"] = "faulty_credentials" except (plexapi.exceptions.NotFound, requests.exceptions.ConnectionError): - _LOGGER.error("Plex server could not be reached: %s", user_input[CONF_URL]) + _LOGGER.error( + "Plex server could not be reached: %s", server_config[CONF_URL] + ) errors["base"] = "not_found" except Exception as error: # pylint: disable=broad-except _LOGGER.error("Unknown error connecting to Plex server: %s", error) @@ -74,7 +73,7 @@ async def async_step_user(self, user_input=None): if errors: return self.async_show_form( - step_id="user", data_schema=self.USER_SCHEMA, errors=errors + step_id="user", data_schema=USER_SCHEMA, errors=errors ) server_id = plex_server.machine_identifier @@ -84,13 +83,13 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="already_configured") url = plex_server.url_in_use - token = user_input.get(CONF_TOKEN) + token = server_config.get(CONF_TOKEN) - server_config = {CONF_URL: url} + entry_config = {CONF_URL: url} if token: - server_config[CONF_TOKEN] = token + entry_config[CONF_TOKEN] = token if url.startswith("https"): - server_config[CONF_VERIFY_SSL] = user_input.get( + entry_config[CONF_VERIFY_SSL] = server_config.get( CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL ) @@ -101,14 +100,13 @@ async def async_step_user(self, user_input=None): data={ CONF_SERVER: plex_server.friendly_name, CONF_SERVER_IDENTIFIER: server_id, - PLEX_SERVER_CONFIG: server_config, + PLEX_SERVER_CONFIG: entry_config, }, ) async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" config = dict(self.current_login) - if user_input is None: configured_servers = [ x.data[CONF_SERVER_IDENTIFIER] for x in self._async_current_entries() @@ -128,9 +126,9 @@ async def async_step_select_server(self, user_input=None): ) config[CONF_SERVER] = available_servers[0] else: - config[CONF_SERVER] = user_input.get(CONF_SERVER) + config[CONF_SERVER] = user_input[CONF_SERVER] - return await self.async_step_user(user_input=config) + return await self.async_step_server_validate(config) async def async_step_discovery(self, discovery_info): """Set default host and port from discovery.""" @@ -151,27 +149,12 @@ async def async_step_discovery(self, discovery_info): CONF_VERIFY_SSL: host_config["verify"], } _LOGGER.info("Imported legacy config, file can be removed: %s", json_file) - return await self.async_step_import(server_config) + return await self.async_step_server_validate(server_config) self.discovery_info = discovery_info return await self.async_step_user() async def async_step_import(self, import_config): """Import from Plex configuration.""" - url = import_config.get(CONF_URL) - token = import_config.get(CONF_TOKEN) - server = import_config.get(CONF_SERVER) - - if url: - config = { - CONF_URL: url, - CONF_TOKEN: token, - CONF_VERIFY_SSL: import_config[CONF_VERIFY_SSL], - } - elif token: - config = {CONF_TOKEN: token, CONF_SERVER: server} - else: - return self.async_abort(reason="invalid_import") - _LOGGER.debug("Imported Plex configuration") - return await self.async_step_user(user_input=config) + return await self.async_step_server_validate(import_config) From aa8a03013b594d7ca60596eee040fa8b5fafec28 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Thu, 12 Sep 2019 14:06:03 -0500 Subject: [PATCH 25/39] Unused without manual setup step --- homeassistant/components/plex/config_flow.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 2d110231b4203f..928d3d1a24fdad 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -34,7 +34,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the Plex flow.""" self.current_login = {} - self.discovery_info = {} self.available_servers = None async def async_step_user(self, user_input=None): @@ -151,7 +150,6 @@ async def async_step_discovery(self, discovery_info): _LOGGER.info("Imported legacy config, file can be removed: %s", json_file) return await self.async_step_server_validate(server_config) - self.discovery_info = discovery_info return await self.async_step_user() async def async_step_import(self, import_config): From 10cd2bd4887d58c50ba53d4db5aea7ff2d7c64f8 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Thu, 12 Sep 2019 14:11:08 -0500 Subject: [PATCH 26/39] Async oops --- homeassistant/components/plex/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 928d3d1a24fdad..817d89b8fdff91 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -136,7 +136,7 @@ async def async_step_discovery(self, discovery_info): return self.async_abort(reason="already_configured") json_file = self.hass.config.path(PLEX_CONFIG_FILE) - file_config = load_json(json_file) + file_config = await self.hass.async_add_executor_job(load_json, json_file) if file_config: host_and_port, host_config = file_config.popitem() From 7dbdc98179b1f8c14d4562f8f2d3143c39e32830 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 14:10:58 -0500 Subject: [PATCH 27/39] First attempt at tests --- tests/components/plex/test_config_flow.py | 265 ++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 tests/components/plex/test_config_flow.py diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py new file mode 100644 index 00000000000000..70423e2dc1d4d0 --- /dev/null +++ b/tests/components/plex/test_config_flow.py @@ -0,0 +1,265 @@ +"""Tests for Plex config flow.""" +from unittest.mock import MagicMock, Mock, patch, PropertyMock +import plexapi.exceptions +import requests.exceptions + +from homeassistant.components.plex import config_flow +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL + +from tests.common import mock_coro + + +class MockAvailableServer: + """Mock avilable server objects.""" + + def __init__(self, name, client_id): + """Initialize the object.""" + self.name = name + self.clientIdentifier = client_id + self.provides = ["server"] + + +class MockConnection: + """Mock a single account resource connection object.""" + + def __init__(self): + """Initialize the object.""" + self.httpuri = "http://1.2.3.4:32400" + self.uri = "http://4.3.2.1:32400" + self.local = True + + +class MockConnections: + """Mock a list of resource connections.""" + + def __init__(self): + """Initialize the object.""" + self.connections = [MockConnection()] + + +async def test_bad_credentials(hass, aioclient_mock): + """Test when provided credentials are rejected.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + with patch( + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + ): + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"]["base"] == "faulty_credentials" + + +async def test_import_file_from_discovery(hass, aioclient_mock): + """Test importing a legacy file during discovery.""" + + mock_file_contents = { + "1.2.3.4:32400": {"ssl": False, "token": "12345", "verify": True} + } + file_host_and_port, file_config = list(mock_file_contents.items())[0] + used_url = f"http://{file_host_and_port}" + + with patch("plexapi.server.PlexServer") as mock_plex_server, patch( + "homeassistant.components.plex.config_flow.load_json", + return_value=mock_coro(mock_file_contents), + ): + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value="Mock Server" + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=used_url + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "discovery"}, + data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + ) + print(result) + + assert result["type"] == "create_entry" + assert result["title"] == "Mock Server" + assert result["data"][config_flow.CONF_SERVER] == "Mock Server" + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] == used_url + assert ( + result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] + == file_config[CONF_TOKEN] + ) + + +async def test_discovery(hass, aioclient_mock): + """Test starting a flow from discovery.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "discovery"}, + data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + +async def test_import_bad_hostname(hass, aioclient_mock): + """Test when an invalid address is provided.""" + + with patch( + "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError + ): + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "import"}, + data={CONF_TOKEN: "12345", CONF_URL: "http://1.2.3.4:32400"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"]["base"] == "not_found" + + +async def test_unknown_exception(hass, aioclient_mock): + """Test when an unknown exception is encountered.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + with patch("plexapi.myplex.MyPlexAccount", side_effect=Exception): + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"}, data={CONF_TOKEN: "12345"} + ) + print(result) + + assert result["type"] == "abort" + assert result["reason"] == "unknown" + + +async def test_no_servers_found(hass, aioclient_mock): + """Test when no servers are on an account.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[]) + + with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account): + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"]["base"] == "no_servers" + + +async def test_single_available_server(hass, aioclient_mock): + """Test creating an entry with one server available.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_connections = MockConnections() + mock_servers = ["Server1"] + server1 = MockAvailableServer("Server1", "1") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( + "plexapi.server.PlexServer" + ) as mock_plex_server: + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value=mock_servers[0] + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=mock_connections.connections[0].httpuri + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "create_entry" + assert result["title"] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + + +async def test_multiple_servers_with_selection(hass, aioclient_mock): + """Test creating an entry with multiple servers available.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_connections = MockConnections() + mock_servers = ["Server1", "Server2"] + server1 = MockAvailableServer("Server1", "1") + server2 = MockAvailableServer("Server2", "2") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( + "plexapi.server.PlexServer" + ) as mock_plex_server: + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value=mock_servers[0] + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=mock_connections.connections[0].httpuri + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "form" + 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]} + ) + + assert result["type"] == "create_entry" + assert result["title"] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" From 0e112d25bfd54d3652fa6928705066b2008681e9 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 14:53:27 -0500 Subject: [PATCH 28/39] Test cleanup --- tests/components/plex/test_config_flow.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 70423e2dc1d4d0..234bdba476c19b 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -6,8 +6,6 @@ from homeassistant.components.plex import config_flow from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL -from tests.common import mock_coro - class MockAvailableServer: """Mock avilable server objects.""" @@ -37,7 +35,7 @@ def __init__(self): self.connections = [MockConnection()] -async def test_bad_credentials(hass, aioclient_mock): +async def test_bad_credentials(hass): """Test when provided credentials are rejected.""" result = await hass.config_entries.flow.async_init( @@ -60,7 +58,7 @@ async def test_bad_credentials(hass, aioclient_mock): assert result["errors"]["base"] == "faulty_credentials" -async def test_import_file_from_discovery(hass, aioclient_mock): +async def test_import_file_from_discovery(hass): """Test importing a legacy file during discovery.""" mock_file_contents = { @@ -71,7 +69,7 @@ async def test_import_file_from_discovery(hass, aioclient_mock): with patch("plexapi.server.PlexServer") as mock_plex_server, patch( "homeassistant.components.plex.config_flow.load_json", - return_value=mock_coro(mock_file_contents), + return_value=mock_file_contents, ): type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -88,7 +86,6 @@ async def test_import_file_from_discovery(hass, aioclient_mock): context={"source": "discovery"}, data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, ) - print(result) assert result["type"] == "create_entry" assert result["title"] == "Mock Server" @@ -101,7 +98,7 @@ async def test_import_file_from_discovery(hass, aioclient_mock): ) -async def test_discovery(hass, aioclient_mock): +async def test_discovery(hass): """Test starting a flow from discovery.""" result = await hass.config_entries.flow.async_init( @@ -114,7 +111,7 @@ async def test_discovery(hass, aioclient_mock): assert result["step_id"] == "user" -async def test_import_bad_hostname(hass, aioclient_mock): +async def test_import_bad_hostname(hass): """Test when an invalid address is provided.""" with patch( @@ -131,7 +128,7 @@ async def test_import_bad_hostname(hass, aioclient_mock): assert result["errors"]["base"] == "not_found" -async def test_unknown_exception(hass, aioclient_mock): +async def test_unknown_exception(hass): """Test when an unknown exception is encountered.""" result = await hass.config_entries.flow.async_init( @@ -145,13 +142,12 @@ async def test_unknown_exception(hass, aioclient_mock): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "user"}, data={CONF_TOKEN: "12345"} ) - print(result) assert result["type"] == "abort" assert result["reason"] == "unknown" -async def test_no_servers_found(hass, aioclient_mock): +async def test_no_servers_found(hass): """Test when no servers are on an account.""" result = await hass.config_entries.flow.async_init( @@ -175,7 +171,7 @@ async def test_no_servers_found(hass, aioclient_mock): assert result["errors"]["base"] == "no_servers" -async def test_single_available_server(hass, aioclient_mock): +async def test_single_available_server(hass): """Test creating an entry with one server available.""" result = await hass.config_entries.flow.async_init( @@ -216,7 +212,7 @@ async def test_single_available_server(hass, aioclient_mock): assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" -async def test_multiple_servers_with_selection(hass, aioclient_mock): +async def test_multiple_servers_with_selection(hass): """Test creating an entry with multiple servers available.""" result = await hass.config_entries.flow.async_init( From 5e71311b7bf8f87d3c3d6e29b50e2a72ae18c4d2 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 16:10:31 -0500 Subject: [PATCH 29/39] Full test coverage for config_flow, enable tests --- .coveragerc | 5 +- tests/components/plex/test_config_flow.py | 158 +++++++++++++++++++++- 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index ad001e5604839e..4c36eddd9c7966 100644 --- a/.coveragerc +++ b/.coveragerc @@ -476,7 +476,10 @@ omit = homeassistant/components/pioneer/media_player.py homeassistant/components/pjlink/media_player.py homeassistant/components/plaato/* - homeassistant/components/plex/* + 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/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 234bdba476c19b..3cee031c5340a0 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -6,6 +6,15 @@ from homeassistant.components.plex import config_flow from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, CONF_URL +from tests.common import MockConfigEntry + + +def init_config_flow(hass): + """Init a configuration flow.""" + flow = config_flow.PlexFlowHandler() + flow.hass = hass + return flow + class MockAvailableServer: """Mock avilable server objects.""" @@ -20,9 +29,10 @@ def __init__(self, name, client_id): class MockConnection: """Mock a single account resource connection object.""" - def __init__(self): + def __init__(self, ssl): """Initialize the object.""" - self.httpuri = "http://1.2.3.4:32400" + prefix = "https" if ssl else "http" + self.httpuri = f"{prefix}://1.2.3.4:32400" self.uri = "http://4.3.2.1:32400" self.local = True @@ -30,9 +40,9 @@ def __init__(self): class MockConnections: """Mock a list of resource connections.""" - def __init__(self): + def __init__(self, ssl=False): """Initialize the object.""" - self.connections = [MockConnection()] + self.connections = [MockConnection(ssl)] async def test_bad_credentials(hass): @@ -111,6 +121,61 @@ async def test_discovery(hass): assert result["step_id"] == "user" +async def test_discovery_while_in_progress(hass): + """Test starting a flow from discovery.""" + + await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "discovery"}, + data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_import_success(hass): + """Test a successful configuration import.""" + + mock_connections = MockConnections(ssl=True) + mock_servers = ["Server1"] + server1 = MockAvailableServer("Server1", "1") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.server.PlexServer") as mock_plex_server: + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value=mock_servers[0] + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=mock_connections.connections[0].httpuri + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "import"}, + data={CONF_TOKEN: "12345", CONF_URL: "https://1.2.3.4:32400"}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert ( + result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] + == mock_connections.connections[0].httpuri + ) + + async def test_import_bad_hostname(hass): """Test when an invalid address is provided.""" @@ -259,3 +324,88 @@ async def test_multiple_servers_with_selection(hass): assert result["title"] == mock_servers[0] assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + + +async def test_adding_last_unconfigured_server(hass): + """Test automatically adding last unconfigured server when multiple servers on account.""" + + MockConfigEntry( + domain=config_flow.DOMAIN, + data={ + config_flow.CONF_SERVER_IDENTIFIER: "unique_id_456", + config_flow.CONF_SERVER: "Server2", + }, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_connections = MockConnections() + mock_servers = ["Server1", "Server2"] + server1 = MockAvailableServer("Server1", "unique_id_123") + server2 = MockAvailableServer("Server2", "unique_id_456") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( + "plexapi.server.PlexServer" + ) as mock_plex_server: + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value=mock_servers[0] + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=mock_connections.connections[0].httpuri + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "create_entry" + assert result["title"] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + + +async def test_already_configured(hass): + """Test a duplicated successful flow.""" + + flow = init_config_flow(hass) + MockConfigEntry( + domain=config_flow.DOMAIN, + data={config_flow.CONF_SERVER_IDENTIFIER: "unique_id_123"}, + ).add_to_hass(hass) + + mock_connections = MockConnections() + mock_servers = ["Server1"] + server1 = MockAvailableServer("Server1", "1") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.server.PlexServer") as mock_plex_server: + type(mock_plex_server.return_value).machineIdentifier = PropertyMock( + return_value="unique_id_123" + ) + type(mock_plex_server.return_value).friendlyName = PropertyMock( + return_value=mock_servers[0] + ) + type(mock_plex_server.return_value)._baseurl = PropertyMock( + return_value=mock_connections.connections[0].httpuri + ) + result = await flow.async_step_import( + {CONF_TOKEN: "12345", CONF_URL: "http://1.2.3.4:32400"} + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" From 37b249bd3ba87517501bc51222db4cb105db7ba0 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 20:16:22 -0500 Subject: [PATCH 30/39] Lint --- tests/components/plex/test_config_flow.py | 58 +++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 3cee031c5340a0..f369e9a5fed43e 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -16,17 +16,17 @@ def init_config_flow(hass): return flow -class MockAvailableServer: +class MockAvailableServer: # pylint: disable=too-few-public-methods """Mock avilable server objects.""" def __init__(self, name, client_id): """Initialize the object.""" self.name = name - self.clientIdentifier = client_id + self.clientIdentifier = client_id # pylint: disable=invalid-name self.provides = ["server"] -class MockConnection: +class MockConnection: # pylint: disable=too-few-public-methods """Mock a single account resource connection object.""" def __init__(self, ssl): @@ -37,7 +37,7 @@ def __init__(self, ssl): self.local = True -class MockConnections: +class MockConnections: # pylint: disable=too-few-public-methods """Mock a list of resource connections.""" def __init__(self, ssl=False): @@ -56,7 +56,7 @@ async def test_bad_credentials(hass): assert result["step_id"] == "user" with patch( - "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized ): result = await hass.config_entries.flow.async_configure( @@ -78,8 +78,8 @@ async def test_import_file_from_discovery(hass): used_url = f"http://{file_host_and_port}" with patch("plexapi.server.PlexServer") as mock_plex_server, patch( - "homeassistant.components.plex.config_flow.load_json", - return_value=mock_file_contents, + "homeassistant.components.plex.config_flow.load_json", + return_value=mock_file_contents, ): type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -87,9 +87,9 @@ async def test_import_file_from_discovery(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value="Mock Server" ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=used_url - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=used_url) result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, @@ -156,9 +156,9 @@ async def test_import_success(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value=mock_servers[0] ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=mock_connections.connections[0].httpuri - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, @@ -180,7 +180,7 @@ async def test_import_bad_hostname(hass): """Test when an invalid address is provided.""" with patch( - "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError + "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError ): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, @@ -255,7 +255,7 @@ async def test_single_available_server(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -263,9 +263,9 @@ async def test_single_available_server(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value=mock_servers[0] ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=mock_connections.connections[0].httpuri - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_TOKEN: "12345"} @@ -297,7 +297,7 @@ async def test_multiple_servers_with_selection(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -305,9 +305,9 @@ async def test_multiple_servers_with_selection(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value=mock_servers[0] ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=mock_connections.connections[0].httpuri - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_TOKEN: "12345"} @@ -354,7 +354,7 @@ async def test_adding_last_unconfigured_server(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -362,9 +362,9 @@ async def test_adding_last_unconfigured_server(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value=mock_servers[0] ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=mock_connections.connections[0].httpuri - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_TOKEN: "12345"} @@ -400,9 +400,9 @@ async def test_already_configured(hass): type(mock_plex_server.return_value).friendlyName = PropertyMock( return_value=mock_servers[0] ) - type(mock_plex_server.return_value)._baseurl = PropertyMock( - return_value=mock_connections.connections[0].httpuri - ) + type( # pylint: disable=protected-access + mock_plex_server.return_value + )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await flow.async_step_import( {CONF_TOKEN: "12345", CONF_URL: "http://1.2.3.4:32400"} ) From b980fb04c96b58156e5f362e7b32fbbccda6918d Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 20:28:44 -0500 Subject: [PATCH 31/39] Fix lint vs black --- tests/components/plex/test_config_flow.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index f369e9a5fed43e..caca716700e6da 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -56,7 +56,7 @@ async def test_bad_credentials(hass): assert result["step_id"] == "user" with patch( - "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized ): result = await hass.config_entries.flow.async_configure( @@ -78,8 +78,8 @@ async def test_import_file_from_discovery(hass): used_url = f"http://{file_host_and_port}" with patch("plexapi.server.PlexServer") as mock_plex_server, patch( - "homeassistant.components.plex.config_flow.load_json", - return_value=mock_file_contents, + "homeassistant.components.plex.config_flow.load_json", + return_value=mock_file_contents, ): type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -180,7 +180,7 @@ async def test_import_bad_hostname(hass): """Test when an invalid address is provided.""" with patch( - "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError + "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError ): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, @@ -255,7 +255,7 @@ async def test_single_available_server(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -297,7 +297,7 @@ async def test_multiple_servers_with_selection(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" @@ -354,7 +354,7 @@ async def test_adding_last_unconfigured_server(hass): mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( - "plexapi.server.PlexServer" + "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( return_value="unique_id_123" From c64eae22f98dfd08f7c0d5e43ce3674f3c44db79 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 20:45:42 -0500 Subject: [PATCH 32/39] Add test init --- tests/components/plex/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/components/plex/__init__.py diff --git a/tests/components/plex/__init__.py b/tests/components/plex/__init__.py new file mode 100644 index 00000000000000..9c9c00d87ace68 --- /dev/null +++ b/tests/components/plex/__init__.py @@ -0,0 +1 @@ +"""Tests for the Plex component.""" From 9286ade86884bc3d59e4940e120af034f5089569 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 21:48:46 -0500 Subject: [PATCH 33/39] Add test package requirement --- script/gen_requirements_all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index ff2943a583b3f8..72fb9ff5a44233 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -110,6 +110,7 @@ "paho-mqtt", "pexpect", "pilight", + "plexapi", "pmsensor", "prometheus_client", "ptvsd", From 06c18e57f061ac5c9e972e238f4175cb73945c79 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Tue, 17 Sep 2019 21:55:54 -0500 Subject: [PATCH 34/39] Actually run script --- requirements_test_all.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c9c32d596bbeb4..f030632e9a0b7c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -249,6 +249,9 @@ pexpect==4.6.0 # homeassistant.components.pilight pilight==0.1.1 +# homeassistant.components.plex +plexapi==3.0.6 + # homeassistant.components.mhz19 # homeassistant.components.serial_pm pmsensor==0.4 From 2a19a9e4002609464d81c9b225c961bf35f692a0 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 18 Sep 2019 09:13:05 -0500 Subject: [PATCH 35/39] Use 'not None' convention --- homeassistant/components/plex/config_flow.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 817d89b8fdff91..660ac367dbf8d6 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -38,12 +38,10 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - if user_input is None: - return self.async_show_form( - step_id="user", data_schema=USER_SCHEMA, errors={} - ) + if user_input is not None: + return await self.async_step_server_validate(user_input) - return await self.async_step_server_validate(user_input) + return self.async_show_form(step_id="user", data_schema=USER_SCHEMA, errors={}) async def async_step_server_validate(self, server_config): """Validate a provided configuration.""" @@ -106,7 +104,9 @@ async def async_step_server_validate(self, server_config): async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" config = dict(self.current_login) - if user_input is None: + if user_input is not None: + config[CONF_SERVER] = user_input[CONF_SERVER] + else: configured_servers = [ x.data[CONF_SERVER_IDENTIFIER] for x in self._async_current_entries() ] @@ -124,8 +124,6 @@ async def async_step_select_server(self, user_input=None): errors={}, ) config[CONF_SERVER] = available_servers[0] - else: - config[CONF_SERVER] = user_input[CONF_SERVER] return await self.async_step_server_validate(config) From 64fee5ecfbb8e95d2d303ddf8ff0c7d349b6a3ff Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 18 Sep 2019 09:15:19 -0500 Subject: [PATCH 36/39] Group exceptions by result --- homeassistant/components/plex/config_flow.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 660ac367dbf8d6..3617cc800dee77 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -51,11 +51,9 @@ async def async_step_server_validate(self, server_config): plex_server = PlexServer(server_config) try: await self.hass.async_add_executor_job(plex_server.connect) + except NoServersFound: errors["base"] = "no_servers" - except ServerNotSpecified as available_servers: - self.available_servers = available_servers.args[0] - return await self.async_step_select_server() except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): _LOGGER.error("Invalid credentials provided, config not created") errors["base"] = "faulty_credentials" @@ -64,6 +62,11 @@ async def async_step_server_validate(self, server_config): "Plex server could not be reached: %s", server_config[CONF_URL] ) errors["base"] = "not_found" + + except ServerNotSpecified as available_servers: + self.available_servers = available_servers.args[0] + return await self.async_step_select_server() + except Exception as error: # pylint: disable=broad-except _LOGGER.error("Unknown error connecting to Plex server: %s", error) return self.async_abort(reason="unknown") From 7c31e71943b6544c79c00acbf7f70fa6e096212d Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 18 Sep 2019 09:48:46 -0500 Subject: [PATCH 37/39] Improve logic, add new error and test --- homeassistant/components/plex/config_flow.py | 50 ++++++++++++-------- homeassistant/components/plex/strings.json | 1 + tests/components/plex/test_config_flow.py | 44 +++++++++++++++++ 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 3617cc800dee77..3c683c802f5ccf 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -7,6 +7,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_URL, CONF_TOKEN, CONF_SSL, CONF_VERIFY_SSL +from homeassistant.core import callback from homeassistant.util.json import load_json from .const import ( # pylint: disable=unused-import @@ -25,6 +26,15 @@ _LOGGER = logging.getLogger(__package__) +@callback +def configured_servers(hass): + """Return a set of the configured Plex servers.""" + return set( + entry.data[CONF_SERVER_IDENTIFIER] + for entry in hass.config_entries.async_entries(DOMAIN) + ) + + class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Plex config flow.""" @@ -109,26 +119,28 @@ async def async_step_select_server(self, user_input=None): config = dict(self.current_login) if user_input is not None: config[CONF_SERVER] = user_input[CONF_SERVER] - else: - configured_servers = [ - x.data[CONF_SERVER_IDENTIFIER] for x in self._async_current_entries() - ] - available_servers = [ - name - for (name, server_id) in self.available_servers - if server_id not in configured_servers - ] - if len(available_servers) > 1: - return self.async_show_form( - step_id="select_server", - data_schema=vol.Schema( - {vol.Required(CONF_SERVER): vol.In(available_servers)} - ), - errors={}, - ) + return await self.async_step_server_validate(config) + + configured = configured_servers(self.hass) + available_servers = [ + name + for (name, server_id) in self.available_servers + if server_id not in configured + ] + + if not available_servers: + return self.async_abort(reason="all_configured") + if len(available_servers) == 1: config[CONF_SERVER] = available_servers[0] - - return await self.async_step_server_validate(config) + return await self.async_step_server_validate(config) + + return self.async_show_form( + step_id="select_server", + data_schema=vol.Schema( + {vol.Required(CONF_SERVER): vol.In(available_servers)} + ), + errors={}, + ) async def async_step_discovery(self, discovery_info): """Set default host and port from discovery.""" diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 551a2fdc3a3f64..396a3387fee295 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -23,6 +23,7 @@ "not_found": "Plex server not found" }, "abort": { + "all_configured": "All linked servers already configured", "already_configured": "This Plex server is already configured", "already_in_progress": "Plex is being configured", "invalid_import": "Imported configuration is invalid", diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index caca716700e6da..47d9c10ce44cd9 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -409,3 +409,47 @@ async def test_already_configured(hass): assert result["type"] == "abort" assert result["reason"] == "already_configured" + + +async def test_all_available_servers_configured(hass): + """Test when all available servers are already configured.""" + + MockConfigEntry( + domain=config_flow.DOMAIN, + data={ + config_flow.CONF_SERVER_IDENTIFIER: "unique_id_123", + config_flow.CONF_SERVER: "Server1", + }, + ).add_to_hass(hass) + + MockConfigEntry( + domain=config_flow.DOMAIN, + data={ + config_flow.CONF_SERVER_IDENTIFIER: "unique_id_456", + config_flow.CONF_SERVER: "Server2", + }, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_connections = MockConnections() + server1 = MockAvailableServer("Server1", "unique_id_123") + server2 = MockAvailableServer("Server2", "unique_id_456") + + mm_plex_account = MagicMock() + mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resource = Mock(return_value=mock_connections) + + with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account): + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "12345"} + ) + + assert result["type"] == "abort" + assert result["reason"] == "all_configured" From cf47a4d7e21ef261ab47be414e69f4a4ff1b0aa7 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 18 Sep 2019 12:36:28 -0500 Subject: [PATCH 38/39] Test cleanup --- tests/components/plex/mock_classes.py | 35 ++++ tests/components/plex/test_config_flow.py | 195 ++++++++++------------ 2 files changed, 124 insertions(+), 106 deletions(-) create mode 100644 tests/components/plex/mock_classes.py diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py new file mode 100644 index 00000000000000..d027087828073c --- /dev/null +++ b/tests/components/plex/mock_classes.py @@ -0,0 +1,35 @@ +"""Mock classes used in tests.""" + +MOCK_HOST_1 = "1.2.3.4" +MOCK_PORT_1 = "32400" +MOCK_HOST_2 = "4.3.2.1" +MOCK_PORT_2 = "32400" + + +class MockAvailableServer: # pylint: disable=too-few-public-methods + """Mock avilable server objects.""" + + def __init__(self, name, client_id): + """Initialize the object.""" + self.name = name + self.clientIdentifier = client_id # pylint: disable=invalid-name + self.provides = ["server"] + + +class MockConnection: # pylint: disable=too-few-public-methods + """Mock a single account resource connection object.""" + + def __init__(self, ssl): + """Initialize the object.""" + prefix = "https" if ssl else "http" + self.httpuri = f"{prefix}://{MOCK_HOST_1}:{MOCK_PORT_1}" + self.uri = "{prefix}://{MOCK_HOST_2}:{MOCK_PORT_2}" + self.local = True + + +class MockConnections: # pylint: disable=too-few-public-methods + """Mock a list of resource connections.""" + + def __init__(self, ssl=False): + """Initialize the object.""" + self.connections = [MockConnection(ssl)] diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 47d9c10ce44cd9..1b4326484af302 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -8,6 +8,19 @@ from tests.common import MockConfigEntry +from .mock_classes import MOCK_HOST_1, MOCK_PORT_1, MockAvailableServer, MockConnections + +MOCK_NAME_1 = "Plex Server 1" +MOCK_ID_1 = "unique_id_123" +MOCK_NAME_2 = "Plex Server 2" +MOCK_ID_2 = "unique_id_456" +MOCK_TOKEN = "secret_token" +MOCK_FILE_CONTENTS = { + f"{MOCK_HOST_1}:{MOCK_PORT_1}": {"ssl": False, "token": MOCK_TOKEN, "verify": True} +} +MOCK_SERVER_1 = MockAvailableServer(MOCK_NAME_1, MOCK_ID_1) +MOCK_SERVER_2 = MockAvailableServer(MOCK_NAME_2, MOCK_ID_2) + def init_config_flow(hass): """Init a configuration flow.""" @@ -16,35 +29,6 @@ def init_config_flow(hass): return flow -class MockAvailableServer: # pylint: disable=too-few-public-methods - """Mock avilable server objects.""" - - def __init__(self, name, client_id): - """Initialize the object.""" - self.name = name - self.clientIdentifier = client_id # pylint: disable=invalid-name - self.provides = ["server"] - - -class MockConnection: # pylint: disable=too-few-public-methods - """Mock a single account resource connection object.""" - - def __init__(self, ssl): - """Initialize the object.""" - prefix = "https" if ssl else "http" - self.httpuri = f"{prefix}://1.2.3.4:32400" - self.uri = "http://4.3.2.1:32400" - self.local = True - - -class MockConnections: # pylint: disable=too-few-public-methods - """Mock a list of resource connections.""" - - def __init__(self, ssl=False): - """Initialize the object.""" - self.connections = [MockConnection(ssl)] - - async def test_bad_credentials(hass): """Test when provided credentials are rejected.""" @@ -60,7 +44,7 @@ async def test_bad_credentials(hass): ): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "form" @@ -71,21 +55,18 @@ async def test_bad_credentials(hass): async def test_import_file_from_discovery(hass): """Test importing a legacy file during discovery.""" - mock_file_contents = { - "1.2.3.4:32400": {"ssl": False, "token": "12345", "verify": True} - } - file_host_and_port, file_config = list(mock_file_contents.items())[0] + file_host_and_port, file_config = list(MOCK_FILE_CONTENTS.items())[0] used_url = f"http://{file_host_and_port}" with patch("plexapi.server.PlexServer") as mock_plex_server, patch( "homeassistant.components.plex.config_flow.load_json", - return_value=mock_file_contents, + return_value=MOCK_FILE_CONTENTS, ): type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_ID_1 ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value="Mock Server" + return_value=MOCK_NAME_1 ) type( # pylint: disable=protected-access mock_plex_server.return_value @@ -94,13 +75,13 @@ async def test_import_file_from_discovery(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "discovery"}, - data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1}, ) assert result["type"] == "create_entry" - assert result["title"] == "Mock Server" - assert result["data"][config_flow.CONF_SERVER] == "Mock Server" - assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["title"] == MOCK_NAME_1 + assert result["data"][config_flow.CONF_SERVER] == MOCK_NAME_1 + assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == MOCK_ID_1 assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] == used_url assert ( result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] @@ -114,7 +95,7 @@ async def test_discovery(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "discovery"}, - data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1}, ) assert result["type"] == "form" @@ -131,7 +112,7 @@ async def test_discovery_while_in_progress(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "discovery"}, - data={CONF_HOST: "1.2.3.4", CONF_PORT: "32400"}, + data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1}, ) assert result["type"] == "abort" @@ -142,19 +123,17 @@ async def test_import_success(hass): """Test a successful configuration import.""" mock_connections = MockConnections(ssl=True) - mock_servers = ["Server1"] - server1 = MockAvailableServer("Server1", "1") mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.server.PlexServer") as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_SERVER_1.clientIdentifier ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value=mock_servers[0] + return_value=MOCK_SERVER_1.name ) type( # pylint: disable=protected-access mock_plex_server.return_value @@ -163,13 +142,19 @@ async def test_import_success(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "import"}, - data={CONF_TOKEN: "12345", CONF_URL: "https://1.2.3.4:32400"}, + data={ + CONF_TOKEN: MOCK_TOKEN, + CONF_URL: f"https://{MOCK_HOST_1}:{MOCK_PORT_1}", + }, ) assert result["type"] == "create_entry" - assert result["title"] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["title"] == MOCK_SERVER_1.name + assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name + assert ( + result["data"][config_flow.CONF_SERVER_IDENTIFIER] + == MOCK_SERVER_1.clientIdentifier + ) assert ( result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] == mock_connections.connections[0].httpuri @@ -185,7 +170,10 @@ async def test_import_bad_hostname(hass): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, context={"source": "import"}, - data={CONF_TOKEN: "12345", CONF_URL: "http://1.2.3.4:32400"}, + data={ + CONF_TOKEN: MOCK_TOKEN, + CONF_URL: f"http://{MOCK_HOST_1}:{MOCK_PORT_1}", + }, ) assert result["type"] == "form" @@ -205,7 +193,9 @@ async def test_unknown_exception(hass): with patch("plexapi.myplex.MyPlexAccount", side_effect=Exception): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"}, data={CONF_TOKEN: "12345"} + config_flow.DOMAIN, + context={"source": "user"}, + data={CONF_TOKEN: MOCK_TOKEN}, ) assert result["type"] == "abort" @@ -228,7 +218,7 @@ async def test_no_servers_found(hass): with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "form" @@ -247,34 +237,35 @@ async def test_single_available_server(hass): assert result["step_id"] == "user" mock_connections = MockConnections() - mock_servers = ["Server1"] - server1 = MockAvailableServer("Server1", "1") mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_SERVER_1.clientIdentifier ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value=mock_servers[0] + return_value=MOCK_SERVER_1.name ) type( # pylint: disable=protected-access mock_plex_server.return_value )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "create_entry" - assert result["title"] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["title"] == MOCK_SERVER_1.name + assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name + assert ( + result["data"][config_flow.CONF_SERVER_IDENTIFIER] + == MOCK_SERVER_1.clientIdentifier + ) async def test_multiple_servers_with_selection(hass): @@ -288,42 +279,41 @@ async def test_multiple_servers_with_selection(hass): assert result["step_id"] == "user" mock_connections = MockConnections() - mock_servers = ["Server1", "Server2"] - server1 = MockAvailableServer("Server1", "1") - server2 = MockAvailableServer("Server2", "2") - mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1, MOCK_SERVER_2]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_SERVER_1.clientIdentifier ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value=mock_servers[0] + return_value=MOCK_SERVER_1.name ) type( # pylint: disable=protected-access mock_plex_server.return_value )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "form" 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]} + result["flow_id"], user_input={config_flow.CONF_SERVER: MOCK_SERVER_1.name} ) assert result["type"] == "create_entry" - assert result["title"] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["title"] == MOCK_SERVER_1.name + assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name + assert ( + result["data"][config_flow.CONF_SERVER_IDENTIFIER] + == MOCK_SERVER_1.clientIdentifier + ) async def test_adding_last_unconfigured_server(hass): @@ -332,8 +322,8 @@ async def test_adding_last_unconfigured_server(hass): MockConfigEntry( domain=config_flow.DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: "unique_id_456", - config_flow.CONF_SERVER: "Server2", + config_flow.CONF_SERVER_IDENTIFIER: MOCK_ID_2, + config_flow.CONF_SERVER: MOCK_NAME_2, }, ).add_to_hass(hass) @@ -345,35 +335,34 @@ async def test_adding_last_unconfigured_server(hass): assert result["step_id"] == "user" mock_connections = MockConnections() - mock_servers = ["Server1", "Server2"] - server1 = MockAvailableServer("Server1", "unique_id_123") - server2 = MockAvailableServer("Server2", "unique_id_456") - mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1, MOCK_SERVER_2]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch( "plexapi.server.PlexServer" ) as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_SERVER_1.clientIdentifier ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value=mock_servers[0] + return_value=MOCK_SERVER_1.name ) type( # pylint: disable=protected-access mock_plex_server.return_value )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "create_entry" - assert result["title"] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER] == mock_servers[0] - assert result["data"][config_flow.CONF_SERVER_IDENTIFIER] == "unique_id_123" + assert result["title"] == MOCK_SERVER_1.name + assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name + assert ( + result["data"][config_flow.CONF_SERVER_IDENTIFIER] + == MOCK_SERVER_1.clientIdentifier + ) async def test_already_configured(hass): @@ -381,30 +370,27 @@ async def test_already_configured(hass): flow = init_config_flow(hass) MockConfigEntry( - domain=config_flow.DOMAIN, - data={config_flow.CONF_SERVER_IDENTIFIER: "unique_id_123"}, + domain=config_flow.DOMAIN, data={config_flow.CONF_SERVER_IDENTIFIER: MOCK_ID_1} ).add_to_hass(hass) mock_connections = MockConnections() - mock_servers = ["Server1"] - server1 = MockAvailableServer("Server1", "1") mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.server.PlexServer") as mock_plex_server: type(mock_plex_server.return_value).machineIdentifier = PropertyMock( - return_value="unique_id_123" + return_value=MOCK_SERVER_1.clientIdentifier ) type(mock_plex_server.return_value).friendlyName = PropertyMock( - return_value=mock_servers[0] + return_value=MOCK_SERVER_1.name ) type( # pylint: disable=protected-access mock_plex_server.return_value )._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri) result = await flow.async_step_import( - {CONF_TOKEN: "12345", CONF_URL: "http://1.2.3.4:32400"} + {CONF_TOKEN: MOCK_TOKEN, CONF_URL: f"http://{MOCK_HOST_1}:{MOCK_PORT_1}"} ) assert result["type"] == "abort" @@ -417,16 +403,16 @@ async def test_all_available_servers_configured(hass): MockConfigEntry( domain=config_flow.DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: "unique_id_123", - config_flow.CONF_SERVER: "Server1", + config_flow.CONF_SERVER_IDENTIFIER: MOCK_ID_1, + config_flow.CONF_SERVER: MOCK_NAME_1, }, ).add_to_hass(hass) MockConfigEntry( domain=config_flow.DOMAIN, data={ - config_flow.CONF_SERVER_IDENTIFIER: "unique_id_456", - config_flow.CONF_SERVER: "Server2", + config_flow.CONF_SERVER_IDENTIFIER: MOCK_ID_2, + config_flow.CONF_SERVER: MOCK_NAME_2, }, ).add_to_hass(hass) @@ -438,17 +424,14 @@ async def test_all_available_servers_configured(hass): assert result["step_id"] == "user" mock_connections = MockConnections() - server1 = MockAvailableServer("Server1", "unique_id_123") - server2 = MockAvailableServer("Server2", "unique_id_456") - mm_plex_account = MagicMock() - mm_plex_account.resources = Mock(return_value=[server1, server2]) + mm_plex_account.resources = Mock(return_value=[MOCK_SERVER_1, MOCK_SERVER_2]) mm_plex_account.resource = Mock(return_value=mock_connections) with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_TOKEN: "12345"} + result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) assert result["type"] == "abort" From c461e6dcaeefc691959a088fcf5af9ef97a2cdb8 Mon Sep 17 00:00:00 2001 From: Jason Lawrence Date: Wed, 18 Sep 2019 12:52:39 -0500 Subject: [PATCH 39/39] Add more asserts --- tests/components/plex/test_config_flow.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 1b4326484af302..9c9c1b625259f9 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -159,6 +159,7 @@ async def test_import_success(hass): result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] == mock_connections.connections[0].httpuri ) + assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_import_bad_hostname(hass): @@ -266,6 +267,11 @@ async def test_single_available_server(hass): result["data"][config_flow.CONF_SERVER_IDENTIFIER] == MOCK_SERVER_1.clientIdentifier ) + assert ( + result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] + == mock_connections.connections[0].httpuri + ) + assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_multiple_servers_with_selection(hass): @@ -314,6 +320,11 @@ async def test_multiple_servers_with_selection(hass): result["data"][config_flow.CONF_SERVER_IDENTIFIER] == MOCK_SERVER_1.clientIdentifier ) + assert ( + result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] + == mock_connections.connections[0].httpuri + ) + assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_adding_last_unconfigured_server(hass): @@ -363,6 +374,11 @@ async def test_adding_last_unconfigured_server(hass): result["data"][config_flow.CONF_SERVER_IDENTIFIER] == MOCK_SERVER_1.clientIdentifier ) + assert ( + result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_URL] + == mock_connections.connections[0].httpuri + ) + assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN async def test_already_configured(hass):