From 7060db318f039294583b3ef3edefe9df407cb602 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 2 Oct 2026 20:33:44 +0200 Subject: [PATCH] Test the connection in the Radio Browser config flow (#184069) --- .../components/radio_browser/config_flow.py | 18 ++++++++-- .../components/radio_browser/strings.json | 3 ++ tests/components/radio_browser/conftest.py | 10 ++++++ .../radio_browser/test_config_flow.py | 35 +++++++++++++++++-- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/radio_browser/config_flow.py b/homeassistant/components/radio_browser/config_flow.py index 220dfb5faee8..095411748ae7 100644 --- a/homeassistant/components/radio_browser/config_flow.py +++ b/homeassistant/components/radio_browser/config_flow.py @@ -2,7 +2,11 @@ from typing import Any, override +from radios import RadioBrowser, RadioBrowserError + from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.const import __version__ +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -17,10 +21,20 @@ class RadioBrowserConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" + errors: dict[str, str] = {} if user_input is not None: - return self.async_create_entry(title="Radio Browser", data={}) + radios = RadioBrowser( + session=async_get_clientsession(self.hass), + user_agent=f"HomeAssistant/{__version__}", + ) + try: + await radios.stats() + except RadioBrowserError: + errors["base"] = "cannot_connect" + else: + return self.async_create_entry(title="Radio Browser", data={}) - return self.async_show_form(step_id="user") + return self.async_show_form(step_id="user", errors=errors) async def async_step_onboarding( self, data: dict[str, Any] | None = None diff --git a/homeassistant/components/radio_browser/strings.json b/homeassistant/components/radio_browser/strings.json index c83c4c86e57a..bf4cf8c45864 100644 --- a/homeassistant/components/radio_browser/strings.json +++ b/homeassistant/components/radio_browser/strings.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, "step": { "user": { "description": "Do you want to add Radio Browser to Home Assistant?" diff --git a/tests/components/radio_browser/conftest.py b/tests/components/radio_browser/conftest.py index c57c92f2a31b..ac32d67d55ca 100644 --- a/tests/components/radio_browser/conftest.py +++ b/tests/components/radio_browser/conftest.py @@ -31,6 +31,16 @@ def mock_setup_entry() -> Generator[AsyncMock]: yield mock_setup +@pytest.fixture +def mock_radio_browser() -> Generator[MagicMock]: + """Mock the Radio Browser client in the config flow.""" + with patch( + "homeassistant.components.radio_browser.config_flow.RadioBrowser", + autospec=True, + ) as radio_browser_mock: + yield radio_browser_mock.return_value + + @pytest.fixture async def init_integration( hass: HomeAssistant, diff --git a/tests/components/radio_browser/test_config_flow.py b/tests/components/radio_browser/test_config_flow.py index a60134ff680a..dfa89046ec1d 100644 --- a/tests/components/radio_browser/test_config_flow.py +++ b/tests/components/radio_browser/test_config_flow.py @@ -1,8 +1,9 @@ """Test the Radio Browser config flow.""" -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest +from radios import RadioBrowserConnectionError from homeassistant.components.radio_browser.const import DOMAIN from homeassistant.config_entries import SOURCE_USER @@ -12,13 +13,14 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry +@pytest.mark.usefixtures("mock_radio_browser") async def test_full_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: """Test the full user configuration flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result.get("type") is FlowResultType.FORM - assert result.get("errors") is None + assert result.get("errors") == {} result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -32,6 +34,35 @@ async def test_full_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) assert len(mock_setup_entry.mock_calls) == 1 +async def test_user_flow_cannot_connect( + hass: HomeAssistant, + mock_radio_browser: MagicMock, + mock_setup_entry: AsyncMock, +) -> None: + """Test the user flow when Radio Browser cannot be reached, and recovers.""" + mock_radio_browser.stats.side_effect = RadioBrowserConnectionError + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result.get("type") is FlowResultType.FORM + assert result.get("errors") == {"base": "cannot_connect"} + + mock_radio_browser.stats.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result.get("type") is FlowResultType.CREATE_ENTRY + assert len(mock_setup_entry.mock_calls) == 1 + + @pytest.mark.usefixtures("mock_setup_entry") async def test_already_configured( hass: HomeAssistant, mock_config_entry: MockConfigEntry