diff --git a/homeassistant/components/rtsp_to_webrtc/config_flow.py b/homeassistant/components/rtsp_to_webrtc/config_flow.py index 235c816b9e9..8594d1faae6 100644 --- a/homeassistant/components/rtsp_to_webrtc/config_flow.py +++ b/homeassistant/components/rtsp_to_webrtc/config_flow.py @@ -9,6 +9,8 @@ import rtsp_to_webrtc import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.hassio import HassioServiceInfo +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -22,6 +24,8 @@ DATA_SCHEMA = vol.Schema({vol.Required(DATA_SERVER_URL): str}) class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """RTSPtoWebRTC config flow.""" + _hassio_discovery: dict[str, Any] + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -41,21 +45,11 @@ class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors={DATA_SERVER_URL: "invalid_url"}, ) - errors = {} - client = rtsp_to_webrtc.client.Client(async_get_clientsession(self.hass), url) - try: - await client.heartbeat() - except rtsp_to_webrtc.exceptions.ResponseError as err: - _LOGGER.error("RTSPtoWebRTC server failure: %s", str(err)) - errors["base"] = "server_failure" - except rtsp_to_webrtc.exceptions.ClientError as err: - _LOGGER.error("RTSPtoWebRTC communication failure: %s", str(err)) - errors["base"] = "server_unreachable" - if errors: + if error_code := await self._test_connection(url): return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, - errors=errors, + errors={"base": error_code}, ) await self.async_set_unique_id(DOMAIN) @@ -63,3 +57,51 @@ class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): title=url, data={DATA_SERVER_URL: url}, ) + + async def _test_connection(self, url: str) -> str | None: + """Test the connection and return any relevant errors.""" + client = rtsp_to_webrtc.client.Client(async_get_clientsession(self.hass), url) + try: + await client.heartbeat() + except rtsp_to_webrtc.exceptions.ResponseError as err: + _LOGGER.error("RTSPtoWebRTC server failure: %s", str(err)) + return "server_failure" + except rtsp_to_webrtc.exceptions.ClientError as err: + _LOGGER.error("RTSPtoWebRTC communication failure: %s", str(err)) + return "server_unreachable" + return None + + async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + """Prepare confiugration for the RTSPtoWebRTC server add-on discovery.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + self._hassio_discovery = discovery_info.config + return await self.async_step_hassio_confirm() + + async def async_step_hassio_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm Add-on discovery.""" + errors = None + if user_input is not None: + # Validate server connection once user has confirmed + host = self._hassio_discovery[CONF_HOST] + port = self._hassio_discovery[CONF_PORT] + url = f"http://{host}:{port}" + if error_code := await self._test_connection(url): + errors = {"base": error_code} + + if user_input is None or errors: + # Show initial confirmation or errors from server validation + return self.async_show_form( + step_id="hassio_confirm", + description_placeholders={"addon": self._hassio_discovery["addon"]}, + data_schema=vol.Schema({}), + errors=errors, + ) + + return self.async_create_entry( + title=self._hassio_discovery["addon"], + data={DATA_SERVER_URL: url}, + ) diff --git a/homeassistant/components/rtsp_to_webrtc/strings.json b/homeassistant/components/rtsp_to_webrtc/strings.json index a2ecea62090..b6bbce17a9a 100644 --- a/homeassistant/components/rtsp_to_webrtc/strings.json +++ b/homeassistant/components/rtsp_to_webrtc/strings.json @@ -7,6 +7,10 @@ "data": { "server_url": "RTSPtoWebRTC server URL e.g. https://example.com" } + }, + "hassio_confirm": { + "title": "RTSPtoWebRTC via Home Assistant add-on", + "description": "Do you want to configure Home Assistant to connect to the RTSPtoWebRTC server provided by the add-on: {addon}?" } }, "error": { diff --git a/tests/components/rtsp_to_webrtc/test_config_flow.py b/tests/components/rtsp_to_webrtc/test_config_flow.py index 94befd0a0b8..ad0cd0006ba 100644 --- a/tests/components/rtsp_to_webrtc/test_config_flow.py +++ b/tests/components/rtsp_to_webrtc/test_config_flow.py @@ -7,6 +7,7 @@ from unittest.mock import patch import rtsp_to_webrtc from homeassistant import config_entries +from homeassistant.components.hassio import HassioServiceInfo from homeassistant.components.rtsp_to_webrtc import DOMAIN from homeassistant.core import HomeAssistant @@ -31,6 +32,7 @@ async def test_web_full_flow(hass: HomeAssistant) -> None: result["flow_id"], {"server_url": "https://example.com"} ) assert result.get("type") == "create_entry" + assert result.get("title") == "https://example.com" assert "result" in result assert result["result"].data == {"server_url": "https://example.com"} @@ -108,3 +110,106 @@ async def test_server_failure(hass: HomeAssistant) -> None: assert result.get("type") == "form" assert result.get("step_id") == "user" assert result.get("errors") == {"base": "server_failure"} + + +async def test_hassio_discovery(hass): + """Test supervisor add-on discovery.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=HassioServiceInfo( + config={ + "addon": "RTSPtoWebRTC", + "host": "fake-server", + "port": 8083, + } + ), + context={"source": config_entries.SOURCE_HASSIO}, + ) + assert result.get("type") == "form" + assert result.get("step_id") == "hassio_confirm" + assert result.get("description_placeholders") == {"addon": "RTSPtoWebRTC"} + + with patch("rtsp_to_webrtc.client.Client.heartbeat"), patch( + "homeassistant.components.rtsp_to_webrtc.async_setup_entry", + return_value=True, + ) as mock_setup: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + + assert result.get("type") == "create_entry" + assert result.get("title") == "RTSPtoWebRTC" + assert "result" in result + assert result["result"].data == {"server_url": "http://fake-server:8083"} + + assert len(mock_setup.mock_calls) == 1 + + +async def test_hassio_single_config_entry(hass: HomeAssistant) -> None: + """Test supervisor add-on discovery only allows a single entry.""" + old_entry = MockConfigEntry(domain=DOMAIN, data={"example": True}) + old_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=HassioServiceInfo( + config={ + "addon": "RTSPtoWebRTC", + "host": "fake-server", + "port": 8083, + } + ), + context={"source": config_entries.SOURCE_HASSIO}, + ) + assert result.get("type") == "abort" + assert result.get("reason") == "single_instance_allowed" + + +async def test_hassio_ignored(hass: HomeAssistant) -> None: + """Test ignoring superversor add-on discovery.""" + old_entry = MockConfigEntry(domain=DOMAIN, source=config_entries.SOURCE_IGNORE) + old_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=HassioServiceInfo( + config={ + "addon": "RTSPtoWebRTC", + "host": "fake-server", + "port": 8083, + } + ), + context={"source": config_entries.SOURCE_HASSIO}, + ) + assert result.get("type") == "abort" + assert result.get("reason") == "single_instance_allowed" + + +async def test_hassio_discovery_server_failure(hass: HomeAssistant) -> None: + """Test server failure during supvervisor add-on discovery shows an error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=HassioServiceInfo( + config={ + "addon": "RTSPtoWebRTC", + "host": "fake-server", + "port": 8083, + } + ), + context={"source": config_entries.SOURCE_HASSIO}, + ) + + assert result.get("type") == "form" + assert result.get("step_id") == "hassio_confirm" + assert not result.get("errors") + assert "flow_id" in result + + with patch( + "rtsp_to_webrtc.client.Client.heartbeat", + side_effect=rtsp_to_webrtc.exceptions.ResponseError(), + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result.get("type") == "form" + assert result.get("step_id") == "hassio_confirm" + assert result.get("errors") == {"base": "server_failure"}