From d2c0688196183bec7a2b00162578e788ebc8db27 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 17 Jul 2023 20:41:25 +0200 Subject: [PATCH] Prevent otbr creating multiple config entries --- homeassistant/components/otbr/config_flow.py | 5 ++ tests/components/otbr/test_config_flow.py | 80 ++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/homeassistant/components/otbr/config_flow.py b/homeassistant/components/otbr/config_flow.py index 67c8412102d6a5..a3fe046409be44 100644 --- a/homeassistant/components/otbr/config_flow.py +++ b/homeassistant/components/otbr/config_flow.py @@ -130,6 +130,11 @@ async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResu url = f"http://{config['host']}:{config['port']}" config_entry_data = {"url": url} + if self._async_in_progress(include_uninitialized=True): + # We currently don't handle multiple config entries, abort if hassio + # discovers multiple addons with otbr support + return self.async_abort(reason="single_instance_allowed") + if current_entries := self._async_current_entries(): for current_entry in current_entries: if current_entry.source != SOURCE_HASSIO: diff --git a/tests/components/otbr/test_config_flow.py b/tests/components/otbr/test_config_flow.py index b6cb0df78cdc47..da25edde04548b 100644 --- a/tests/components/otbr/test_config_flow.py +++ b/tests/components/otbr/test_config_flow.py @@ -23,6 +23,12 @@ slug="otbr", uuid="12345", ) +HASSIO_DATA_2 = hassio.HassioServiceInfo( + config={"host": "core-silabs-multiprotocol_2", "port": 8082}, + name="Silicon Labs Multiprotocol", + slug="other_addon", + uuid="23456", +) @pytest.fixture(name="addon_info") @@ -313,6 +319,80 @@ async def test_hassio_discovery_flow_sky_connect( assert config_entry.unique_id == HASSIO_DATA.uuid +async def test_hassio_discovery_flow_2x_addons( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info +) -> None: + """Test the hassio discovery flow when the user has 2 addons with otbr support.""" + url1 = "http://core-silabs-multiprotocol:8081" + url2 = "http://core-silabs-multiprotocol_2:8081" + aioclient_mock.get(f"{url1}/node/dataset/active", text="aa") + aioclient_mock.get(f"{url2}/node/dataset/active", text="bb") + + async def _addon_info(hass, slug): + await asyncio.sleep(0) + if slug == "otbr": + return { + "available": True, + "hostname": None, + "options": { + "device": ( + "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_" + "9e2adbd75b8beb119fe564a0f320645d-if00-port0" + ) + }, + "state": None, + "update_available": False, + "version": None, + } + return { + "available": True, + "hostname": None, + "options": { + "device": ( + "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_" + "9e2adbd75b8beb119fe564a0f320645d-if00-port1" + ) + }, + "state": None, + "update_available": False, + "version": None, + } + + addon_info.side_effect = _addon_info + + with patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + results = await asyncio.gather( + hass.config_entries.flow.async_init( + otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA + ), + hass.config_entries.flow.async_init( + otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA_2 + ), + ) + + expected_data = { + "url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']}", + } + + assert results[0]["type"] == FlowResultType.CREATE_ENTRY + assert results[0]["title"] == "Home Assistant SkyConnect" + assert results[0]["data"] == expected_data + assert results[0]["options"] == {} + assert results[1]["type"] == FlowResultType.ABORT + assert results[1]["reason"] == "single_instance_allowed" + assert len(hass.config_entries.async_entries(otbr.DOMAIN)) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] + assert config_entry.data == expected_data + assert config_entry.options == {} + assert config_entry.title == "Home Assistant SkyConnect" + assert config_entry.unique_id == HASSIO_DATA.uuid + + async def test_hassio_discovery_flow_router_not_setup( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info ) -> None: