Plex external config flow (#26936)

* Plex external auth config flow

* Update requirements_all

* Test dependency

* Bad await, delay variable

* Use hass aiohttp session, bump plexauth

* Bump requirements

* Bump library version again

* Use callback view instead of polling

* Update tests for callback view

* Reduce timeout with callback

* Review feedback

* F-string

* Wrap sync call

* Unused

* Revert unnecessary async wrap
This commit is contained in:
jjlawren 2019-10-01 10:20:30 -05:00 committed by Martin Hjelmare
parent c1851a2d94
commit 571ab5a978
10 changed files with 267 additions and 77 deletions

View file

@ -2,10 +2,14 @@
import copy
import logging
from aiohttp import web_response
import plexapi.exceptions
from plexauth import PlexAuth
import requests.exceptions
import voluptuous as vol
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant import config_entries
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.const import (
@ -20,6 +24,8 @@ from homeassistant.core import callback
from homeassistant.util.json import load_json
from .const import ( # pylint: disable=unused-import
AUTH_CALLBACK_NAME,
AUTH_CALLBACK_PATH,
CONF_SERVER,
CONF_SERVER_IDENTIFIER,
CONF_USE_EPISODE_ART,
@ -30,13 +36,15 @@ from .const import ( # pylint: disable=unused-import
DOMAIN,
PLEX_CONFIG_FILE,
PLEX_SERVER_CONFIG,
X_PLEX_DEVICE_NAME,
X_PLEX_VERSION,
X_PLEX_PRODUCT,
X_PLEX_PLATFORM,
)
from .errors import NoServersFound, ServerNotSpecified
from .server import PlexServer
USER_SCHEMA = vol.Schema(
{vol.Optional(CONF_TOKEN): str, vol.Optional("manual_setup"): bool}
)
USER_SCHEMA = vol.Schema({vol.Optional("manual_setup"): bool})
_LOGGER = logging.getLogger(__package__)
@ -67,6 +75,8 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self.current_login = {}
self.discovery_info = {}
self.available_servers = None
self.plexauth = None
self.token = None
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
@ -74,9 +84,8 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
if user_input.pop("manual_setup", False):
return await self.async_step_manual_setup(user_input)
if CONF_TOKEN in user_input:
return await self.async_step_server_validate(user_input)
errors[CONF_TOKEN] = "no_token"
return await self.async_step_plex_website_auth()
return self.async_show_form(
step_id="user", data_schema=USER_SCHEMA, errors=errors
@ -225,6 +234,43 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.debug("Imported Plex configuration")
return await self.async_step_server_validate(import_config)
async def async_step_plex_website_auth(self):
"""Begin external auth flow on Plex website."""
self.hass.http.register_view(PlexAuthorizationCallbackView)
payload = {
"X-Plex-Device-Name": X_PLEX_DEVICE_NAME,
"X-Plex-Version": X_PLEX_VERSION,
"X-Plex-Product": X_PLEX_PRODUCT,
"X-Plex-Device": self.hass.config.location_name,
"X-Plex-Platform": X_PLEX_PLATFORM,
"X-Plex-Model": "Plex OAuth",
}
session = async_get_clientsession(self.hass)
self.plexauth = PlexAuth(payload, session)
await self.plexauth.initiate_auth()
forward_url = f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
auth_url = self.plexauth.auth_url(forward_url)
return self.async_external_step(step_id="obtain_token", url=auth_url)
async def async_step_obtain_token(self, user_input=None):
"""Obtain token after external auth completed."""
token = await self.plexauth.token(10)
if not token:
return self.async_external_step_done(next_step_id="timed_out")
self.token = token
return self.async_external_step_done(next_step_id="use_external_token")
async def async_step_timed_out(self, user_input=None):
"""Abort flow when time expires."""
return self.async_abort(reason="token_request_timeout")
async def async_step_use_external_token(self, user_input=None):
"""Continue server validation with external token."""
server_config = {CONF_TOKEN: self.token}
return await self.async_step_server_validate(server_config)
class PlexOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Plex options."""
@ -263,3 +309,23 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow):
}
),
)
class PlexAuthorizationCallbackView(HomeAssistantView):
"""Handle callback from external auth."""
url = AUTH_CALLBACK_PATH
name = AUTH_CALLBACK_NAME
requires_auth = False
async def get(self, request):
"""Receive authorization confirmation."""
hass = request.app["hass"]
await hass.config_entries.flow.async_configure(
flow_id=request.query["flow_id"], user_input=None
)
return web_response.Response(
headers={"content-type": "text/html"},
text="<script>window.close()</script>Success! This window can be closed",
)

View file

@ -1,4 +1,6 @@
"""Constants for the Plex component."""
from homeassistant.const import __version__
DOMAIN = "plex"
NAME_FORMAT = "Plex {}"
@ -18,3 +20,11 @@ CONF_SERVER = "server"
CONF_SERVER_IDENTIFIER = "server_id"
CONF_USE_EPISODE_ART = "use_episode_art"
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
AUTH_CALLBACK_PATH = "/auth/plex/callback"
AUTH_CALLBACK_NAME = "auth:plex:callback"
X_PLEX_DEVICE_NAME = "Home Assistant"
X_PLEX_PLATFORM = "Home Assistant"
X_PLEX_PRODUCT = "Home Assistant"
X_PLEX_VERSION = __version__

View file

@ -4,7 +4,8 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/plex",
"requirements": [
"plexapi==3.0.6"
"plexapi==3.0.6",
"plexauth==0.0.4"
],
"dependencies": [],
"codeowners": [

View file

@ -11,9 +11,21 @@ from .const import (
CONF_SHOW_ALL_CONTROLS,
CONF_USE_EPISODE_ART,
DEFAULT_VERIFY_SSL,
X_PLEX_DEVICE_NAME,
X_PLEX_PLATFORM,
X_PLEX_PRODUCT,
X_PLEX_VERSION,
)
from .errors import NoServersFound, ServerNotSpecified
# Set default headers sent by plexapi
plexapi.X_PLEX_DEVICE_NAME = X_PLEX_DEVICE_NAME
plexapi.X_PLEX_PLATFORM = X_PLEX_PLATFORM
plexapi.X_PLEX_PRODUCT = X_PLEX_PRODUCT
plexapi.X_PLEX_VERSION = X_PLEX_VERSION
plexapi.myplex.BASE_HEADERS = plexapi.reset_base_headers()
plexapi.server.BASE_HEADERS = plexapi.reset_base_headers()
class PlexServer:
"""Manages a single Plex server connection."""

View file

@ -21,9 +21,8 @@
},
"user": {
"title": "Connect Plex server",
"description": "Enter a Plex token for automatic setup or manually configure a server.",
"description": "Continue to authorize at plex.tv or manually configure a server.",
"data": {
"token": "Plex token",
"manual_setup": "Manual setup"
}
}
@ -31,14 +30,14 @@
"error": {
"faulty_credentials": "Authorization failed",
"no_servers": "No servers linked to account",
"not_found": "Plex server not found",
"no_token": "Provide a token or select manual setup"
"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",
"token_request_timeout": "Timed out obtaining token",
"unknown": "Failed for unknown reason"
}
},

View file

@ -964,6 +964,9 @@ pizzapi==0.0.3
# homeassistant.components.plex
plexapi==3.0.6
# homeassistant.components.plex
plexauth==0.0.4
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11

View file

@ -261,6 +261,9 @@ pillow==6.1.0
# homeassistant.components.plex
plexapi==3.0.6
# homeassistant.components.plex
plexauth==0.0.4
# homeassistant.components.mhz19
# homeassistant.components.serial_pm
pmsensor==0.4

View file

@ -113,6 +113,7 @@ TEST_REQUIREMENTS = (
"pilight",
"pillow",
"plexapi",
"plexauth",
"pmsensor",
"prometheus_client",
"ptvsd",

View file

@ -1,9 +1,9 @@
"""Mock classes used in tests."""
MOCK_HOST_1 = "1.2.3.4"
MOCK_PORT_1 = "32400"
MOCK_PORT_1 = 32400
MOCK_HOST_2 = "4.3.2.1"
MOCK_PORT_2 = "32400"
MOCK_PORT_2 = 32400
class MockAvailableServer: # pylint: disable=too-few-public-methods

View file

@ -1,5 +1,7 @@
"""Tests for Plex config flow."""
from unittest.mock import MagicMock, Mock, patch, PropertyMock
import asynctest
import plexapi.exceptions
import requests.exceptions
@ -12,6 +14,7 @@ from homeassistant.const import (
CONF_TOKEN,
CONF_URL,
)
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
@ -49,19 +52,28 @@ async def test_bad_credentials(hass):
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={"manual_setup": True}
)
assert result["type"] == "form"
assert result["step_id"] == "manual_setup"
with patch(
"plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
user_input={
CONF_HOST: MOCK_HOST_1,
CONF_PORT: MOCK_PORT_1,
CONF_SSL: False,
CONF_VERIFY_SSL: False,
CONF_TOKEN: "BAD TOKEN",
},
)
assert result["type"] == "form"
assert result["step_id"] == "user"
assert result["errors"]["base"] == "faulty_credentials"
@ -92,7 +104,6 @@ async def test_import_file_from_discovery(hass):
context={"source": "discovery"},
data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1},
)
assert result["type"] == "create_entry"
assert result["title"] == MOCK_NAME_1
assert result["data"][config_flow.CONF_SERVER] == MOCK_NAME_1
@ -112,7 +123,6 @@ async def test_discovery(hass):
context={"source": "discovery"},
data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1},
)
assert result["type"] == "form"
assert result["step_id"] == "user"
@ -129,7 +139,6 @@ async def test_discovery_while_in_progress(hass):
context={"source": "discovery"},
data={CONF_HOST: MOCK_HOST_1, CONF_PORT: MOCK_PORT_1},
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
@ -191,7 +200,6 @@ async def test_import_bad_hostname(hass):
CONF_URL: f"http://{MOCK_HOST_1}:{MOCK_PORT_1}",
},
)
assert result["type"] == "form"
assert result["step_id"] == "user"
assert result["errors"]["base"] == "not_found"
@ -203,15 +211,25 @@ async def test_unknown_exception(hass):
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: MOCK_TOKEN, "manual_setup": False},
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"manual_setup": True}
)
assert result["type"] == "form"
assert result["step_id"] == "manual_setup"
with patch("plexapi.server.PlexServer", side_effect=Exception):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST_1,
CONF_PORT: MOCK_PORT_1,
CONF_SSL: True,
CONF_VERIFY_SSL: True,
CONF_TOKEN: MOCK_TOKEN,
},
)
assert result["type"] == "abort"
@ -221,23 +239,32 @@ async def test_unknown_exception(hass):
async def test_no_servers_found(hass):
"""Test when no servers are on an account."""
await async_setup_component(hass, "http", {"http": {}})
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):
with patch(
"plexapi.myplex.MyPlexAccount", return_value=mm_plex_account
), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "form"
assert result["step_id"] == "user"
assert result["errors"]["base"] == "no_servers"
@ -246,10 +273,11 @@ async def test_no_servers_found(hass):
async def test_single_available_server(hass):
"""Test creating an entry with one server available."""
await async_setup_component(hass, "http", {"http": {}})
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
@ -261,7 +289,11 @@ async def test_single_available_server(hass):
with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch(
"plexapi.server.PlexServer"
) as mock_plex_server:
) as mock_plex_server, asynctest.patch(
"plexauth.PlexAuth.initiate_auth"
), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
type(mock_plex_server.return_value).machineIdentifier = PropertyMock(
return_value=MOCK_SERVER_1.clientIdentifier
)
@ -273,10 +305,14 @@ async def test_single_available_server(hass):
)._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "create_entry"
assert result["title"] == MOCK_SERVER_1.name
assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name
@ -294,10 +330,11 @@ async def test_single_available_server(hass):
async def test_multiple_servers_with_selection(hass):
"""Test creating an entry with multiple servers available."""
await async_setup_component(hass, "http", {"http": {}})
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
@ -308,7 +345,11 @@ async def test_multiple_servers_with_selection(hass):
with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch(
"plexapi.server.PlexServer"
) as mock_plex_server:
) as mock_plex_server, asynctest.patch(
"plexauth.PlexAuth.initiate_auth"
), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
type(mock_plex_server.return_value).machineIdentifier = PropertyMock(
return_value=MOCK_SERVER_1.clientIdentifier
)
@ -320,17 +361,20 @@ async def test_multiple_servers_with_selection(hass):
)._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
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_SERVER_1.name}
)
assert result["type"] == "create_entry"
assert result["title"] == MOCK_SERVER_1.name
assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name
@ -348,6 +392,8 @@ async def test_multiple_servers_with_selection(hass):
async def test_adding_last_unconfigured_server(hass):
"""Test automatically adding last unconfigured server when multiple servers on account."""
await async_setup_component(hass, "http", {"http": {}})
MockConfigEntry(
domain=config_flow.DOMAIN,
data={
@ -359,7 +405,6 @@ async def test_adding_last_unconfigured_server(hass):
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
@ -370,7 +415,11 @@ async def test_adding_last_unconfigured_server(hass):
with patch("plexapi.myplex.MyPlexAccount", return_value=mm_plex_account), patch(
"plexapi.server.PlexServer"
) as mock_plex_server:
) as mock_plex_server, asynctest.patch(
"plexauth.PlexAuth.initiate_auth"
), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
type(mock_plex_server.return_value).machineIdentifier = PropertyMock(
return_value=MOCK_SERVER_1.clientIdentifier
)
@ -382,10 +431,14 @@ async def test_adding_last_unconfigured_server(hass):
)._baseurl = PropertyMock(return_value=mock_connections.connections[0].httpuri)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "create_entry"
assert result["title"] == MOCK_SERVER_1.name
assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name
@ -414,7 +467,9 @@ async def test_already_configured(hass):
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:
with patch("plexapi.server.PlexServer") as mock_plex_server, asynctest.patch(
"plexauth.PlexAuth.initiate_auth"
), asynctest.patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN):
type(mock_plex_server.return_value).machineIdentifier = PropertyMock(
return_value=MOCK_SERVER_1.clientIdentifier
)
@ -424,10 +479,10 @@ async def test_already_configured(hass):
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: MOCK_TOKEN, CONF_URL: f"http://{MOCK_HOST_1}:{MOCK_PORT_1}"}
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
@ -435,6 +490,8 @@ async def test_already_configured(hass):
async def test_all_available_servers_configured(hass):
"""Test when all available servers are already configured."""
await async_setup_component(hass, "http", {"http": {}})
MockConfigEntry(
domain=config_flow.DOMAIN,
data={
@ -454,7 +511,6 @@ async def test_all_available_servers_configured(hass):
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
@ -463,13 +519,21 @@ async def test_all_available_servers_configured(hass):
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):
with patch(
"plexapi.myplex.MyPlexAccount", return_value=mm_plex_account
), asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_TOKEN: MOCK_TOKEN, "manual_setup": False},
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "abort"
assert result["reason"] == "all_configured"
@ -480,14 +544,12 @@ async def test_manual_config(hass):
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_TOKEN: "", "manual_setup": True}
result["flow_id"], user_input={"manual_setup": True}
)
assert result["type"] == "form"
assert result["step_id"] == "manual_setup"
@ -508,13 +570,12 @@ async def test_manual_config(hass):
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST_1,
CONF_PORT: int(MOCK_PORT_1),
CONF_PORT: MOCK_PORT_1,
CONF_SSL: True,
CONF_VERIFY_SSL: True,
CONF_TOKEN: MOCK_TOKEN,
},
)
assert result["type"] == "create_entry"
assert result["title"] == MOCK_SERVER_1.name
assert result["data"][config_flow.CONF_SERVER] == MOCK_SERVER_1.name
@ -529,25 +590,6 @@ async def test_manual_config(hass):
assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
async def test_no_token(hass):
"""Test failing when no token provided."""
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
assert result["errors"][CONF_TOKEN] == "no_token"
async def test_option_flow(hass):
"""Test config flow selection of one of two bridges."""
@ -557,7 +599,6 @@ async def test_option_flow(hass):
result = await hass.config_entries.options.flow.async_init(
entry.entry_id, context={"source": "test"}, data=None
)
assert result["type"] == "form"
assert result["step_id"] == "plex_mp_settings"
@ -575,3 +616,57 @@ async def test_option_flow(hass):
config_flow.CONF_SHOW_ALL_CONTROLS: True,
}
}
async def test_external_timed_out(hass):
"""Test when external flow times out."""
await async_setup_component(hass, "http", {"http": {}})
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
with asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch(
"plexauth.PlexAuth.token", return_value=None
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "external_done"
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == "abort"
assert result["reason"] == "token_request_timeout"
async def test_callback_view(hass, aiohttp_client):
"""Test callback view."""
await async_setup_component(hass, "http", {"http": {}})
result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
with asynctest.patch("plexauth.PlexAuth.initiate_auth"), asynctest.patch(
"plexauth.PlexAuth.token", return_value=MOCK_TOKEN
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"manual_setup": False}
)
assert result["type"] == "external"
client = await aiohttp_client(hass.http.app)
forward_url = f'{config_flow.AUTH_CALLBACK_PATH}?flow_id={result["flow_id"]}'
resp = await client.get(forward_url)
assert resp.status == 200