Adjust the GitHub config flow (#105295)
parent
2d5176d1f6
commit
b5012a9964
|
@ -2,7 +2,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from contextlib import suppress
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from aiogithubapi import (
|
from aiogithubapi import (
|
||||||
GitHubAPI,
|
GitHubAPI,
|
||||||
|
@ -17,7 +18,7 @@ import voluptuous as vol
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult, UnknownFlow
|
||||||
from homeassistant.helpers.aiohttp_client import (
|
from homeassistant.helpers.aiohttp_client import (
|
||||||
SERVER_SOFTWARE,
|
SERVER_SOFTWARE,
|
||||||
async_get_clientsession,
|
async_get_clientsession,
|
||||||
|
@ -118,19 +119,27 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle device steps."""
|
"""Handle device steps."""
|
||||||
|
|
||||||
async def _wait_for_login() -> None:
|
async def _wait_for_login() -> None:
|
||||||
# mypy is not aware that we can't get here without having these set already
|
if TYPE_CHECKING:
|
||||||
assert self._device is not None
|
# mypy is not aware that we can't get here without having these set already
|
||||||
assert self._login_device is not None
|
assert self._device is not None
|
||||||
|
assert self._login_device is not None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self._device.activation(
|
response = await self._device.activation(
|
||||||
device_code=self._login_device.device_code
|
device_code=self._login_device.device_code
|
||||||
)
|
)
|
||||||
self._login = response.data
|
self._login = response.data
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.hass.async_create_task(
|
|
||||||
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
async def _progress():
|
||||||
)
|
# If the user closes the dialog the flow will no longer exist and it will raise UnknownFlow
|
||||||
|
with suppress(UnknownFlow):
|
||||||
|
await self.hass.config_entries.flow.async_configure(
|
||||||
|
flow_id=self.flow_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self.hass.async_create_task(_progress())
|
||||||
|
|
||||||
if not self._device:
|
if not self._device:
|
||||||
self._device = GitHubDeviceAPI(
|
self._device = GitHubDeviceAPI(
|
||||||
|
@ -139,31 +148,33 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
**{"client_name": SERVER_SOFTWARE},
|
**{"client_name": SERVER_SOFTWARE},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self._device.register()
|
response = await self._device.register()
|
||||||
self._login_device = response.data
|
self._login_device = response.data
|
||||||
except GitHubException as exception:
|
except GitHubException as exception:
|
||||||
LOGGER.exception(exception)
|
LOGGER.exception(exception)
|
||||||
return self.async_abort(reason="could_not_register")
|
return self.async_abort(reason="could_not_register")
|
||||||
|
|
||||||
if not self.login_task:
|
if self.login_task is None:
|
||||||
self.login_task = self.hass.async_create_task(_wait_for_login())
|
self.login_task = self.hass.async_create_task(_wait_for_login())
|
||||||
return self.async_show_progress(
|
|
||||||
step_id="device",
|
|
||||||
progress_action="wait_for_device",
|
|
||||||
description_placeholders={
|
|
||||||
"url": OAUTH_USER_LOGIN,
|
|
||||||
"code": self._login_device.user_code,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
if self.login_task.done():
|
||||||
await self.login_task
|
if self.login_task.exception():
|
||||||
except GitHubException as exception:
|
return self.async_show_progress_done(next_step_id="could_not_register")
|
||||||
LOGGER.exception(exception)
|
return self.async_show_progress_done(next_step_id="repositories")
|
||||||
return self.async_show_progress_done(next_step_id="could_not_register")
|
|
||||||
|
|
||||||
return self.async_show_progress_done(next_step_id="repositories")
|
if TYPE_CHECKING:
|
||||||
|
# mypy is not aware that we can't get here without having this set already
|
||||||
|
assert self._login_device is not None
|
||||||
|
|
||||||
|
return self.async_show_progress(
|
||||||
|
step_id="device",
|
||||||
|
progress_action="wait_for_device",
|
||||||
|
description_placeholders={
|
||||||
|
"url": OAUTH_USER_LOGIN,
|
||||||
|
"code": self._login_device.user_code,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_repositories(
|
async def async_step_repositories(
|
||||||
self,
|
self,
|
||||||
|
@ -171,8 +182,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle repositories step."""
|
"""Handle repositories step."""
|
||||||
|
|
||||||
# mypy is not aware that we can't get here without having this set already
|
if TYPE_CHECKING:
|
||||||
assert self._login is not None
|
# mypy is not aware that we can't get here without having this set already
|
||||||
|
assert self._login is not None
|
||||||
|
|
||||||
if not user_input:
|
if not user_input:
|
||||||
repositories = await get_repositories(self.hass, self._login.access_token)
|
repositories = await get_repositories(self.hass, self._login.access_token)
|
||||||
|
@ -208,6 +220,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return OptionsFlowHandler(config_entry)
|
return OptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_remove(self) -> None:
|
||||||
|
"""Handle remove handler callback."""
|
||||||
|
if self.login_task and not self.login_task.done():
|
||||||
|
# Clean up login task if it's still running
|
||||||
|
self.login_task.cancel()
|
||||||
|
|
||||||
|
|
||||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Handle a option flow for GitHub."""
|
"""Handle a option flow for GitHub."""
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from aiogithubapi import GitHubException
|
from aiogithubapi import GitHubException
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.github.config_flow import get_repositories
|
from homeassistant.components.github.config_flow import get_repositories
|
||||||
|
@ -12,7 +13,7 @@ from homeassistant.components.github.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType, UnknownFlow
|
||||||
|
|
||||||
from .common import MOCK_ACCESS_TOKEN
|
from .common import MOCK_ACCESS_TOKEN
|
||||||
|
|
||||||
|
@ -126,6 +127,44 @@ async def test_flow_with_activation_failure(
|
||||||
assert result["step_id"] == "could_not_register"
|
assert result["step_id"] == "could_not_register"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_with_remove_while_activating(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test flow with user canceling while activating."""
|
||||||
|
aioclient_mock.post(
|
||||||
|
"https://github.com/login/device/code",
|
||||||
|
json={
|
||||||
|
"device_code": "3584d83530557fdd1f46af8289938c8ef79f9dc5",
|
||||||
|
"user_code": "WDJB-MJHT",
|
||||||
|
"verification_uri": "https://github.com/login/device",
|
||||||
|
"expires_in": 900,
|
||||||
|
"interval": 5,
|
||||||
|
},
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
aioclient_mock.post(
|
||||||
|
"https://github.com/login/oauth/access_token",
|
||||||
|
json={"error": "authorization_pending"},
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
)
|
||||||
|
assert result["step_id"] == "device"
|
||||||
|
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||||
|
|
||||||
|
assert hass.config_entries.flow.async_get(result["flow_id"])
|
||||||
|
|
||||||
|
# Simulate user canceling the flow
|
||||||
|
hass.config_entries.flow._async_remove_flow_progress(result["flow_id"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with pytest.raises(UnknownFlow):
|
||||||
|
hass.config_entries.flow.async_get(result["flow_id"])
|
||||||
|
|
||||||
|
|
||||||
async def test_already_configured(
|
async def test_already_configured(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
|
Loading…
Reference in New Issue