diff --git a/homeassistant/components/cambridge_audio/config_flow.py b/homeassistant/components/cambridge_audio/config_flow.py index ca587ee9a48..6f5a92feac0 100644 --- a/homeassistant/components/cambridge_audio/config_flow.py +++ b/homeassistant/components/cambridge_audio/config_flow.py @@ -7,12 +7,18 @@ from aiostreammagic import StreamMagicClient import voluptuous as vol from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_RECONFIGURE, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONNECT_TIMEOUT, DOMAIN, STREAM_MAGIC_EXCEPTIONS +DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) + class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN): """Cambridge Audio configuration flow.""" @@ -64,6 +70,17 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN): }, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration of the integration.""" + if not user_input: + return self.async_show_form( + step_id="reconfigure", + data_schema=DATA_SCHEMA, + ) + return await self.async_step_user(user_input) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -82,6 +99,12 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id( client.info.unit_id, raise_on_progress=False ) + if self.source == SOURCE_RECONFIGURE: + self._abort_if_unique_id_mismatch(reason="wrong_device") + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates={CONF_HOST: user_input[CONF_HOST]}, + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=client.info.name, @@ -91,6 +114,6 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN): await client.disconnect() return self.async_show_form( step_id="user", - data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + data_schema=DATA_SCHEMA, errors=errors, ) diff --git a/homeassistant/components/cambridge_audio/quality_scale.yaml b/homeassistant/components/cambridge_audio/quality_scale.yaml index 65b921268f4..e5cafdd6368 100644 --- a/homeassistant/components/cambridge_audio/quality_scale.yaml +++ b/homeassistant/components/cambridge_audio/quality_scale.yaml @@ -56,7 +56,7 @@ rules: diagnostics: done exception-translations: done icon-translations: done - reconfiguration-flow: todo + reconfiguration-flow: done dynamic-devices: status: exempt comment: | diff --git a/homeassistant/components/cambridge_audio/strings.json b/homeassistant/components/cambridge_audio/strings.json index c368ba060a7..9f5e031815b 100644 --- a/homeassistant/components/cambridge_audio/strings.json +++ b/homeassistant/components/cambridge_audio/strings.json @@ -13,12 +13,23 @@ }, "discovery_confirm": { "description": "Do you want to setup {name}?" + }, + "reconfigure": { + "description": "Reconfigure your Cambridge Audio Streamer.", + "data": { + "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "[%key:component::cambridge_audio::config::step::user::data_description::host%]" + } } }, "error": { "cannot_connect": "Failed to connect to Cambridge Audio device. Please make sure the device is powered up and connected to the network. Try power-cycling the device if it does not connect." }, "abort": { + "wrong_device": "This Cambridge Audio device does not match the existing device id. Please make sure you entered the correct IP address.", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } diff --git a/tests/components/cambridge_audio/test_config_flow.py b/tests/components/cambridge_audio/test_config_flow.py index 9a2d077b8f8..8d01db6e015 100644 --- a/tests/components/cambridge_audio/test_config_flow.py +++ b/tests/components/cambridge_audio/test_config_flow.py @@ -7,7 +7,7 @@ from aiostreammagic import StreamMagicError from homeassistant.components.cambridge_audio.const import DOMAIN from homeassistant.components.zeroconf import ZeroconfServiceInfo -from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -192,3 +192,55 @@ async def test_zeroconf_duplicate( ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" + + +async def _start_reconfigure_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> ConfigFlowResult: + """Initialize a reconfigure flow.""" + mock_config_entry.add_to_hass(hass) + + reconfigure_result = await mock_config_entry.start_reconfigure_flow(hass) + + assert reconfigure_result["type"] is FlowResultType.FORM + assert reconfigure_result["step_id"] == "reconfigure" + + return await hass.config_entries.flow.async_configure( + reconfigure_result["flow_id"], + {CONF_HOST: "192.168.20.219"}, + ) + + +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_stream_magic_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfigure flow.""" + + result = await _start_reconfigure_flow(hass, mock_config_entry) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id) + assert entry + assert entry.data == { + CONF_HOST: "192.168.20.219", + } + + +async def test_reconfigure_unique_id_mismatch( + hass: HomeAssistant, + mock_stream_magic_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Ensure reconfigure flow aborts when the bride changes.""" + mock_stream_magic_client.info.unit_id = "different_udn" + + result = await _start_reconfigure_flow(hass, mock_config_entry) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "wrong_device"