mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Add config flow to frontier_silicon (#64365)
* Add config_flow to frontier_silicon * Add missing translation file * Delay unique_id validation until radio_id can be determined * Fix tests * Improve tests * Use FlowResultType * Bump afsapi to 0.2.6 * Fix requirements_test_all.txt * Stash ssdp, reauth and unignore flows for now * Re-introduce SSDP flow * hassfest changes * Address review comments * Small style update * Fix tests * Update integrations.json * fix order in manifest.json * fix black errors * Apply suggestions from code review Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Address review comments * fix black errors * Use async_setup_platform instead of async_setup * Address review comments on tests * parameterize tests * Remove discovery component changes from this PR * Address review comments * Apply suggestions from code review Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Add extra asserts to tests * Restructure _async_step_device_config_if_needed * Add return statement * Update homeassistant/components/frontier_silicon/media_player.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
@ -395,7 +395,8 @@ omit =
|
||||
homeassistant/components/fritzbox_callmonitor/__init__.py
|
||||
homeassistant/components/fritzbox_callmonitor/base.py
|
||||
homeassistant/components/fritzbox_callmonitor/sensor.py
|
||||
homeassistant/components/frontier_silicon/const.py
|
||||
homeassistant/components/frontier_silicon/__init__.py
|
||||
homeassistant/components/frontier_silicon/browse_media.py
|
||||
homeassistant/components/frontier_silicon/media_player.py
|
||||
homeassistant/components/futurenow/light.py
|
||||
homeassistant/components/garadget/cover.py
|
||||
|
@ -401,6 +401,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/frontend/ @home-assistant/frontend
|
||||
/tests/components/frontend/ @home-assistant/frontend
|
||||
/homeassistant/components/frontier_silicon/ @wlcrs
|
||||
/tests/components/frontier_silicon/ @wlcrs
|
||||
/homeassistant/components/fully_kiosk/ @cgarwood
|
||||
/tests/components/fully_kiosk/ @cgarwood
|
||||
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
|
||||
|
@ -1 +1,45 @@
|
||||
"""The frontier_silicon component."""
|
||||
"""The Frontier Silicon integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from afsapi import AFSAPI, ConnectionError as FSConnectionError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
|
||||
from .const import CONF_PIN, CONF_WEBFSAPI_URL, DOMAIN
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Frontier Silicon from a config entry."""
|
||||
|
||||
webfsapi_url = entry.data[CONF_WEBFSAPI_URL]
|
||||
pin = entry.data[CONF_PIN]
|
||||
|
||||
afsapi = AFSAPI(webfsapi_url, pin)
|
||||
|
||||
try:
|
||||
await afsapi.get_power()
|
||||
except FSConnectionError as exception:
|
||||
raise PlatformNotReady from exception
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = afsapi
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
178
homeassistant/components/frontier_silicon/config_flow.py
Normal file
178
homeassistant/components/frontier_silicon/config_flow.py
Normal file
@ -0,0 +1,178 @@
|
||||
"""Config flow for Frontier Silicon Media Player integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from afsapi import AFSAPI, ConnectionError as FSConnectionError, InvalidPinException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import CONF_PIN, CONF_WEBFSAPI_URL, DEFAULT_PIN, DEFAULT_PORT, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
)
|
||||
|
||||
STEP_DEVICE_CONFIG_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_PIN,
|
||||
default=DEFAULT_PIN,
|
||||
): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Frontier Silicon Media Player."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
|
||||
self._webfsapi_url: str | None = None
|
||||
self._name: str | None = None
|
||||
self._unique_id: str | None = None
|
||||
|
||||
async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult:
|
||||
"""Handle the import of legacy configuration.yaml entries."""
|
||||
|
||||
device_url = f"http://{import_info[CONF_HOST]}:{import_info[CONF_PORT]}/device"
|
||||
try:
|
||||
self._webfsapi_url = await AFSAPI.get_webfsapi_endpoint(device_url)
|
||||
except FSConnectionError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception(exception)
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
try:
|
||||
afsapi = AFSAPI(self._webfsapi_url, import_info[CONF_PIN])
|
||||
|
||||
self._unique_id = await afsapi.get_radio_id()
|
||||
except FSConnectionError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except InvalidPinException:
|
||||
return self.async_abort(reason="invalid_auth")
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception(exception)
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
await self.async_set_unique_id(self._unique_id, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
self._name = import_info[CONF_NAME] or "Radio"
|
||||
|
||||
return await self._create_entry(pin=import_info[CONF_PIN])
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step of manual configuration."""
|
||||
errors = {}
|
||||
|
||||
if user_input:
|
||||
device_url = (
|
||||
f"http://{user_input[CONF_HOST]}:{user_input[CONF_PORT]}/device"
|
||||
)
|
||||
try:
|
||||
self._webfsapi_url = await AFSAPI.get_webfsapi_endpoint(device_url)
|
||||
except FSConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception(exception)
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return await self._async_step_device_config_if_needed()
|
||||
|
||||
data_schema = self.add_suggested_values_to_schema(
|
||||
STEP_USER_DATA_SCHEMA, user_input
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def _async_step_device_config_if_needed(self) -> FlowResult:
|
||||
"""Most users will not have changed the default PIN on their radio.
|
||||
|
||||
We try to use this default PIN, and only if this fails ask for it via `async_step_device_config`
|
||||
"""
|
||||
|
||||
try:
|
||||
# try to login with default pin
|
||||
afsapi = AFSAPI(self._webfsapi_url, DEFAULT_PIN)
|
||||
|
||||
self._name = await afsapi.get_friendly_name()
|
||||
except InvalidPinException:
|
||||
# Ask for a PIN
|
||||
return await self.async_step_device_config()
|
||||
|
||||
self.context["title_placeholders"] = {"name": self._name}
|
||||
|
||||
self._unique_id = await afsapi.get_radio_id()
|
||||
await self.async_set_unique_id(self._unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return await self._create_entry()
|
||||
|
||||
async def async_step_device_config(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle device configuration step.
|
||||
|
||||
We ask for the PIN in this step.
|
||||
"""
|
||||
assert self._webfsapi_url is not None
|
||||
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="device_config", data_schema=STEP_DEVICE_CONFIG_DATA_SCHEMA
|
||||
)
|
||||
|
||||
errors = {}
|
||||
|
||||
try:
|
||||
afsapi = AFSAPI(self._webfsapi_url, user_input[CONF_PIN])
|
||||
|
||||
self._name = await afsapi.get_friendly_name()
|
||||
|
||||
except FSConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidPinException:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception(exception)
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
self._unique_id = await afsapi.get_radio_id()
|
||||
await self.async_set_unique_id(self._unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
return await self._create_entry(pin=user_input[CONF_PIN])
|
||||
|
||||
data_schema = self.add_suggested_values_to_schema(
|
||||
STEP_DEVICE_CONFIG_DATA_SCHEMA, user_input
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="device_config",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def _create_entry(self, pin: str | None = None) -> FlowResult:
|
||||
"""Create the entry."""
|
||||
assert self._name is not None
|
||||
assert self._webfsapi_url is not None
|
||||
|
||||
data = {CONF_WEBFSAPI_URL: self._webfsapi_url, CONF_PIN: pin or DEFAULT_PIN}
|
||||
|
||||
return self.async_create_entry(title=self._name, data=data)
|
@ -1,6 +1,9 @@
|
||||
"""Constants for the Frontier Silicon Media Player integration."""
|
||||
DOMAIN = "frontier_silicon"
|
||||
|
||||
CONF_WEBFSAPI_URL = "webfsapi_url"
|
||||
CONF_PIN = "pin"
|
||||
|
||||
DEFAULT_PIN = "1234"
|
||||
DEFAULT_PORT = 80
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
"domain": "frontier_silicon",
|
||||
"name": "Frontier Silicon",
|
||||
"codeowners": ["@wlcrs"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontier_silicon",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["afsapi==0.2.7"]
|
||||
|
@ -21,15 +21,17 @@ from homeassistant.components.media_player import (
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .browse_media import browse_node, browse_top_level
|
||||
from .const import DEFAULT_PIN, DEFAULT_PORT, DOMAIN, MEDIA_CONTENT_ID_PRESET
|
||||
from .const import CONF_PIN, DEFAULT_PIN, DEFAULT_PORT, DOMAIN, MEDIA_CONTENT_ID_PRESET
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -49,7 +51,11 @@ async def async_setup_platform(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Frontier Silicon platform."""
|
||||
"""Set up the Frontier Silicon platform.
|
||||
|
||||
YAML is deprecated, and imported automatically.
|
||||
SSDP discovery is temporarily retained - to be refactor subsequently.
|
||||
"""
|
||||
if discovery_info is not None:
|
||||
webfsapi_url = await AFSAPI.get_webfsapi_endpoint(
|
||||
discovery_info["ssdp_description"]
|
||||
@ -61,24 +67,41 @@ async def async_setup_platform(
|
||||
[AFSAPIDevice(name, afsapi)],
|
||||
True,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
name = config.get(CONF_NAME)
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"remove_yaml",
|
||||
breaks_in_ha_version="2023.6.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="removed_yaml",
|
||||
)
|
||||
|
||||
try:
|
||||
webfsapi_url = await AFSAPI.get_webfsapi_endpoint(
|
||||
f"http://{host}:{port}/device"
|
||||
)
|
||||
except FSConnectionError:
|
||||
_LOGGER.error(
|
||||
"Could not add the FSAPI device at %s:%s -> %s", host, port, password
|
||||
)
|
||||
return
|
||||
afsapi = AFSAPI(webfsapi_url, password)
|
||||
async_add_entities([AFSAPIDevice(name, afsapi)], True)
|
||||
await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_NAME: config.get(CONF_NAME),
|
||||
CONF_HOST: config.get(CONF_HOST),
|
||||
CONF_PORT: config.get(CONF_PORT, DEFAULT_PORT),
|
||||
CONF_PIN: config.get(CONF_PASSWORD, DEFAULT_PIN),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Frontier Silicon entity."""
|
||||
|
||||
afsapi: AFSAPI = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
async_add_entities([AFSAPIDevice(config_entry.title, afsapi)], True)
|
||||
|
||||
|
||||
class AFSAPIDevice(MediaPlayerEntity):
|
||||
|
35
homeassistant/components/frontier_silicon/strings.json
Normal file
35
homeassistant/components/frontier_silicon/strings.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Frontier Silicon Setup",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
}
|
||||
},
|
||||
"device_config": {
|
||||
"title": "Device Configuration",
|
||||
"description": "The pin can be found via 'MENU button > Main Menu > System setting > Network > NetRemote PIN setup'",
|
||||
"data": {
|
||||
"pin": "[%key:common::config_flow::data::pin%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"removed_yaml": {
|
||||
"title": "The Frontier Silicon YAML configuration has been removed",
|
||||
"description": "Configuring Frontier Silicon using YAML has been removed.\n\nYour existing YAML configuration is not used by Home Assistant.\n\nRemove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
}
|
||||
}
|
||||
}
|
@ -145,6 +145,7 @@ FLOWS = {
|
||||
"fritzbox",
|
||||
"fritzbox_callmonitor",
|
||||
"fronius",
|
||||
"frontier_silicon",
|
||||
"fully_kiosk",
|
||||
"garages_amsterdam",
|
||||
"gdacs",
|
||||
|
@ -1818,7 +1818,7 @@
|
||||
"frontier_silicon": {
|
||||
"name": "Frontier Silicon",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"fully_kiosk": {
|
||||
|
@ -78,6 +78,9 @@ adguardhome==0.6.1
|
||||
# homeassistant.components.advantage_air
|
||||
advantage_air==0.4.1
|
||||
|
||||
# homeassistant.components.frontier_silicon
|
||||
afsapi==0.2.7
|
||||
|
||||
# homeassistant.components.agent_dvr
|
||||
agent-py==0.0.23
|
||||
|
||||
|
1
tests/components/frontier_silicon/__init__.py
Normal file
1
tests/components/frontier_silicon/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Frontier Silicon integration."""
|
59
tests/components/frontier_silicon/conftest.py
Normal file
59
tests/components/frontier_silicon/conftest.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""Configuration for frontier_silicon tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.frontier_silicon.const import (
|
||||
CONF_PIN,
|
||||
CONF_WEBFSAPI_URL,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_entry() -> MockConfigEntry:
|
||||
"""Create a mock Frontier Silicon config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="mock_radio_id",
|
||||
data={CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi", CONF_PIN: "1234"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_valid_device_url() -> Generator[None, None, None]:
|
||||
"""Return a valid webfsapi endpoint."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_webfsapi_endpoint",
|
||||
return_value="http://1.1.1.1:80/webfsapi",
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_valid_pin() -> Generator[None, None, None]:
|
||||
"""Make get_friendly_name return a value, indicating a valid pin."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_friendly_name",
|
||||
return_value="Name of the device",
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_radio_id() -> Generator[None, None, None]:
|
||||
"""Return a valid radio_id."""
|
||||
with patch("afsapi.AFSAPI.get_radio_id", return_value="mock_radio_id"):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.frontier_silicon.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
266
tests/components/frontier_silicon/test_config_flow.py
Normal file
266
tests/components/frontier_silicon/test_config_flow.py
Normal file
@ -0,0 +1,266 @@
|
||||
"""Test the Frontier Silicon config flow."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from afsapi import ConnectionError, InvalidPinException
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.frontier_silicon.const import CONF_WEBFSAPI_URL, DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_import_success(hass: HomeAssistant) -> None:
|
||||
"""Test successful import."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_HOST: "1.1.1.1",
|
||||
CONF_PORT: 80,
|
||||
CONF_PIN: "1234",
|
||||
CONF_NAME: "Test name",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Test name"
|
||||
assert result["data"] == {
|
||||
CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi",
|
||||
CONF_PIN: "1234",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("webfsapi_endpoint_error", "result_reason"),
|
||||
[
|
||||
(ConnectionError, "cannot_connect"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_import_webfsapi_endpoint_failures(
|
||||
hass: HomeAssistant, webfsapi_endpoint_error: Exception, result_reason: str
|
||||
) -> None:
|
||||
"""Test various failure of get_webfsapi_endpoint."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_webfsapi_endpoint",
|
||||
side_effect=webfsapi_endpoint_error,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_HOST: "1.1.1.1",
|
||||
CONF_PORT: 80,
|
||||
CONF_PIN: "1234",
|
||||
CONF_NAME: "Test name",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == result_reason
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("radio_id_error", "result_reason"),
|
||||
[
|
||||
(ConnectionError, "cannot_connect"),
|
||||
(InvalidPinException, "invalid_auth"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_import_radio_id_failures(
|
||||
hass: HomeAssistant, radio_id_error: Exception, result_reason: str
|
||||
) -> None:
|
||||
"""Test various failure of get_radio_id."""
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_radio_id",
|
||||
side_effect=radio_id_error,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_HOST: "1.1.1.1",
|
||||
CONF_PORT: 80,
|
||||
CONF_PIN: "1234",
|
||||
CONF_NAME: "Test name",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == result_reason
|
||||
|
||||
|
||||
async def test_import_already_exists(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test import of device which already exists."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_HOST: "1.1.1.1",
|
||||
CONF_PORT: 80,
|
||||
CONF_PIN: "1234",
|
||||
CONF_NAME: "Test name",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_form_default_pin(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test manual device add with default pin."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Name of the device"
|
||||
assert result2["data"] == {
|
||||
CONF_WEBFSAPI_URL: "http://1.1.1.1:80/webfsapi",
|
||||
CONF_PIN: "1234",
|
||||
}
|
||||
mock_setup_entry.assert_called_once()
|
||||
|
||||
|
||||
async def test_form_nondefault_pin(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_friendly_name",
|
||||
side_effect=InvalidPinException,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result2["errors"] is None
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result3["title"] == "Name of the device"
|
||||
assert result3["data"] == {
|
||||
"webfsapi_url": "http://1.1.1.1:80/webfsapi",
|
||||
"pin": "4321",
|
||||
}
|
||||
mock_setup_entry.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("friendly_name_error", "result_error"),
|
||||
[
|
||||
(ConnectionError, "cannot_connect"),
|
||||
(InvalidPinException, "invalid_auth"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_form_nondefault_pin_invalid(
|
||||
hass: HomeAssistant, friendly_name_error: Exception, result_error: str
|
||||
) -> None:
|
||||
"""Test we get the proper errors when trying to validate an user-provided PIN."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_friendly_name",
|
||||
side_effect=InvalidPinException,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result2["errors"] is None
|
||||
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_friendly_name",
|
||||
side_effect=friendly_name_error,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_PIN: "4321"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "device_config"
|
||||
assert result3["errors"] == {"base": result_error}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("webfsapi_endpoint_error", "result_error"),
|
||||
[
|
||||
(ConnectionError, "cannot_connect"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_invalid_device_url(
|
||||
hass: HomeAssistant, webfsapi_endpoint_error: Exception, result_error: str
|
||||
) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"afsapi.AFSAPI.get_webfsapi_endpoint",
|
||||
side_effect=webfsapi_endpoint_error,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "1.1.1.1", CONF_PORT: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": result_error}
|
Reference in New Issue
Block a user