Update OTRB config entry if REST API port has changed (#90101)

* Update OTRB config entry if REST API port has changed

* Improve test coverage
This commit is contained in:
Erik Montnemery 2023-03-22 14:03:39 +01:00 committed by GitHub
parent 0ecd043cb2
commit 130c8ea5f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 9 deletions

View File

@ -155,6 +155,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_warn_on_default_network_settings(hass, entry, dataset_tlvs) _warn_on_default_network_settings(hass, entry, dataset_tlvs)
await async_add_dataset(hass, DOMAIN, dataset_tlvs.hex()) await async_add_dataset(hass, DOMAIN, dataset_tlvs.hex())
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
hass.data[DOMAIN] = otbrdata hass.data[DOMAIN] = otbrdata
return True return True
@ -166,6 +168,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_get_active_dataset_tlvs(hass: HomeAssistant) -> bytes | None: async def async_get_active_dataset_tlvs(hass: HomeAssistant) -> bytes | None:
"""Get current active operational dataset in TLVS format, or None. """Get current active operational dataset in TLVS format, or None.

View File

@ -8,10 +8,11 @@ import aiohttp
import python_otbr_api import python_otbr_api
from python_otbr_api import tlv_parser from python_otbr_api import tlv_parser
import voluptuous as vol import voluptuous as vol
import yarl
from homeassistant.components.hassio import HassioServiceInfo from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.components.thread import async_get_preferred_dataset from homeassistant.components.thread import async_get_preferred_dataset
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import SOURCE_HASSIO, 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 homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -86,11 +87,25 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
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."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
config = discovery_info.config config = discovery_info.config
url = f"http://{config['host']}:{config['port']}" url = f"http://{config['host']}:{config['port']}"
config_entry_data = {"url": url}
if current_entries := self._async_current_entries():
for current_entry in current_entries:
if current_entry.source != SOURCE_HASSIO:
continue
current_url = yarl.URL(current_entry.data["url"])
if (
current_url.host != config["host"]
or current_url.port == config["port"]
):
continue
# Update URL with the new port
self.hass.config_entries.async_update_entry(
current_entry, data=config_entry_data
)
return self.async_abort(reason="single_instance_allowed")
try: try:
await self._connect_and_create_dataset(url) await self._connect_and_create_dataset(url)
@ -101,5 +116,5 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(DOMAIN) await self.async_set_unique_id(DOMAIN)
return self.async_create_entry( return self.async_create_entry(
title="Open Thread Border Router", title="Open Thread Border Router",
data={"url": url}, data=config_entry_data,
) )

View File

@ -1,6 +1,7 @@
"""Test the Open Thread Border Router config flow.""" """Test the Open Thread Border Router config flow."""
import asyncio import asyncio
from http import HTTPStatus from http import HTTPStatus
from typing import Any
from unittest.mock import patch from unittest.mock import patch
import aiohttp import aiohttp
@ -373,8 +374,69 @@ async def test_hassio_discovery_flow_404(
assert result["reason"] == "unknown" assert result["reason"] == "unknown"
@pytest.mark.parametrize("source", ("hassio", "user")) async def test_hassio_discovery_flow_new_port(hass: HomeAssistant) -> None:
async def test_config_flow_single_entry(hass: HomeAssistant, source: str) -> None: """Test the port can be updated."""
mock_integration(hass, MockModule("hassio"))
# Setup the config entry
config_entry = MockConfigEntry(
data={
"url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']+1}"
},
domain=otbr.DOMAIN,
options={},
source="hassio",
title="Open Thread Border Router",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
expected_data = {
"url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']}",
}
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
assert config_entry.data == expected_data
async def test_hassio_discovery_flow_new_port_other_addon(hass: HomeAssistant) -> None:
"""Test the port is not updated if we get data for another addon hosting OTBR."""
mock_integration(hass, MockModule("hassio"))
# Setup the config entry
config_entry = MockConfigEntry(
data={"url": f"http://openthread_border_router:{HASSIO_DATA.config['port']+1}"},
domain=otbr.DOMAIN,
options={},
source="hassio",
title="Open Thread Border Router",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
# Make sure the data was not updated
expected_data = {
"url": f"http://openthread_border_router:{HASSIO_DATA.config['port']+1}",
}
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
assert config_entry.data == expected_data
@pytest.mark.parametrize(("source", "data"), [("hassio", HASSIO_DATA), ("user", None)])
async def test_config_flow_single_entry(
hass: HomeAssistant, source: str, data: Any
) -> 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"))
@ -392,7 +454,7 @@ async def test_config_flow_single_entry(hass: HomeAssistant, source: str) -> Non
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": source} otbr.DOMAIN, context={"source": source}, data=data
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT

View File

@ -1,7 +1,7 @@
"""Test the Open Thread Border Router integration.""" """Test the Open Thread Border Router integration."""
import asyncio import asyncio
from http import HTTPStatus from http import HTTPStatus
from unittest.mock import patch from unittest.mock import ANY, AsyncMock, MagicMock, patch
import aiohttp import aiohttp
import pytest import pytest
@ -100,6 +100,31 @@ async def test_config_entry_not_ready(hass: HomeAssistant, error) -> None:
assert not await hass.config_entries.async_setup(config_entry.entry_id) assert not await hass.config_entries.async_setup(config_entry.entry_id)
async def test_config_entry_update(hass: HomeAssistant) -> None:
"""Test update config entry settings."""
config_entry = MockConfigEntry(
data=CONFIG_ENTRY_DATA,
domain=otbr.DOMAIN,
options={},
title="My OTBR",
)
config_entry.add_to_hass(hass)
mock_api = MagicMock()
mock_api.get_active_dataset_tlvs = AsyncMock(return_value=None)
with patch("python_otbr_api.OTBR", return_value=mock_api) as mock_otrb_api:
assert await hass.config_entries.async_setup(config_entry.entry_id)
mock_otrb_api.assert_called_once_with(CONFIG_ENTRY_DATA["url"], ANY, ANY)
new_config_entry_data = {"url": "http://core-silabs-multiprotocol:8082"}
assert CONFIG_ENTRY_DATA["url"] != new_config_entry_data["url"]
with patch("python_otbr_api.OTBR", return_value=mock_api) as mock_otrb_api:
hass.config_entries.async_update_entry(config_entry, data=new_config_entry_data)
await hass.async_block_till_done()
mock_otrb_api.assert_called_once_with(new_config_entry_data["url"], ANY, ANY)
async def test_remove_entry( async def test_remove_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, otbr_config_entry hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, otbr_config_entry
) -> None: ) -> None: