Adjust Thread config flow (#86097)

* Adjust Thread config flow

* Improve tests

* Update homeassistant/components/otbr/config_flow.py

Co-authored-by: Franck Nijhof <git@frenck.dev>

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Erik Montnemery 2023-01-17 19:27:33 +01:00 committed by GitHub
parent bd1c476edf
commit b4abfb1697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 15 deletions

View File

@ -1,12 +1,14 @@
"""Config flow for the Open Thread Border Router integration.""" """Config flow for the Open Thread Border Router integration."""
from __future__ import annotations from __future__ import annotations
import python_otbr_api
import voluptuous as vol import voluptuous as vol
from homeassistant.components.hassio import HassioServiceInfo from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_URL from homeassistant.const import CONF_URL
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN from .const import DOMAIN
@ -23,14 +25,26 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
if self._async_current_entries(): if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed") return self.async_abort(reason="single_instance_allowed")
errors = {}
if user_input is not None: if user_input is not None:
return self.async_create_entry( url = user_input[CONF_URL]
title="Thread", api = python_otbr_api.OTBR(url, async_get_clientsession(self.hass), 10)
data={"url": user_input[CONF_URL]}, try:
) await api.get_active_dataset_tlvs()
except python_otbr_api.OTBRError:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(DOMAIN)
return self.async_create_entry(
title="Thread",
data=user_input,
)
data_schema = vol.Schema({CONF_URL: str}) data_schema = vol.Schema({CONF_URL: str})
return self.async_show_form(step_id="user", data_schema=data_schema) return self.async_show_form(
step_id="user", data_schema=data_schema, errors=errors
)
async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
"""Handle hassio discovery.""" """Handle hassio discovery."""
@ -38,7 +52,9 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="single_instance_allowed") return self.async_abort(reason="single_instance_allowed")
config = discovery_info.config config = discovery_info.config
url = f"http://{config['host']}:{config['port']}"
await self.async_set_unique_id(DOMAIN)
return self.async_create_entry( return self.async_create_entry(
title="Thread", title="Thread",
data={"url": f"http://{config['host']}:{config['port']}"}, data={"url": url},
) )

View File

@ -3,7 +3,7 @@
"after_dependencies": ["hassio"], "after_dependencies": ["hassio"],
"domain": "otbr", "domain": "otbr",
"iot_class": "local_polling", "iot_class": "local_polling",
"config_flow": false, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/otbr", "documentation": "https://www.home-assistant.io/integrations/otbr",
"integration_type": "system", "integration_type": "system",
"name": "Thread", "name": "Thread",

View File

@ -8,6 +8,9 @@
"description": "Provide URL for the Open Thread Border Router's REST API" "description": "Provide URL for the Open Thread Border Router's REST API"
} }
}, },
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
} }

View File

@ -0,0 +1,18 @@
{
"config": {
"abort": {
"already_configured": "Service is already configured"
},
"error": {
"cannot_connect": "Failed to connect"
},
"step": {
"user": {
"data": {
"url": "URL"
},
"description": "Provide URL for the Open Thread Border Router's REST API"
}
}
}
}

View File

@ -304,6 +304,7 @@ FLOWS = {
"openuv", "openuv",
"openweathermap", "openweathermap",
"oralb", "oralb",
"otbr",
"overkiz", "overkiz",
"ovo_energy", "ovo_energy",
"owntracks", "owntracks",

View File

@ -1,11 +1,15 @@
"""Test the Open Thread Border Router config flow.""" """Test the Open Thread Border Router config flow."""
from http import HTTPStatus
from unittest.mock import patch from unittest.mock import patch
import pytest
from homeassistant.components import hassio, otbr from homeassistant.components import hassio, otbr
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry, MockModule, mock_integration from tests.common import MockConfigEntry, MockModule, mock_integration
from tests.test_util.aiohttp import AiohttpClientMocker
HASSIO_DATA = hassio.HassioServiceInfo( HASSIO_DATA = hassio.HassioServiceInfo(
config={"host": "blah", "port": "bluh"}, config={"host": "blah", "port": "bluh"},
@ -14,16 +18,20 @@ HASSIO_DATA = hassio.HassioServiceInfo(
) )
async def test_user_flow(hass: HomeAssistant) -> None: async def test_user_flow(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test the user flow.""" """Test the user flow."""
url = "http://custom_url:1234"
aioclient_mock.get(f"{url}/node/dataset/active", text="aa")
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
otbr.DOMAIN, context={"source": "user"} otbr.DOMAIN, context={"source": "user"}
) )
expected_data = {"url": "http://custom_url:1234"} expected_data = {"url": url}
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["errors"] is None assert result["errors"] == {}
with patch( with patch(
"homeassistant.components.otbr.async_setup_entry", "homeassistant.components.otbr.async_setup_entry",
@ -32,7 +40,7 @@ async def test_user_flow(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"url": "http://custom_url:1234", "url": url,
}, },
) )
assert result["type"] == FlowResultType.CREATE_ENTRY assert result["type"] == FlowResultType.CREATE_ENTRY
@ -45,7 +53,30 @@ async def test_user_flow(hass: HomeAssistant) -> None:
assert config_entry.data == expected_data assert config_entry.data == expected_data
assert config_entry.options == {} assert config_entry.options == {}
assert config_entry.title == "Thread" assert config_entry.title == "Thread"
assert config_entry.unique_id is None assert config_entry.unique_id == otbr.DOMAIN
async def test_user_flow_404(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test the user flow."""
url = "http://custom_url:1234"
aioclient_mock.get(f"{url}/node/dataset/active", status=HTTPStatus.NOT_FOUND)
result = await hass.config_entries.flow.async_init(
otbr.DOMAIN, context={"source": "user"}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"url": url,
},
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}
async def test_hassio_discovery_flow(hass: HomeAssistant) -> None: async def test_hassio_discovery_flow(hass: HomeAssistant) -> None:
@ -72,10 +103,11 @@ async def test_hassio_discovery_flow(hass: HomeAssistant) -> None:
assert config_entry.data == expected_data assert config_entry.data == expected_data
assert config_entry.options == {} assert config_entry.options == {}
assert config_entry.title == "Thread" assert config_entry.title == "Thread"
assert config_entry.unique_id is None assert config_entry.unique_id == otbr.DOMAIN
async def test_config_flow_single_entry(hass: HomeAssistant) -> None: @pytest.mark.parametrize("source", ("hassio", "user"))
async def test_config_flow_single_entry(hass: HomeAssistant, source: str) -> None:
"""Test only a single entry is allowed.""" """Test only a single entry is allowed."""
mock_integration(hass, MockModule("hassio")) mock_integration(hass, MockModule("hassio"))
@ -93,7 +125,7 @@ async def test_config_flow_single_entry(hass: HomeAssistant) -> None:
return_value=True, return_value=True,
) as mock_setup_entry: ) as mock_setup_entry:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA otbr.DOMAIN, context={"source": source}
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT