diff --git a/homeassistant/components/bosch_alarm/config_flow.py b/homeassistant/components/bosch_alarm/config_flow.py index 4b1e3e511fc..9e664e49ca9 100644 --- a/homeassistant/components/bosch_alarm/config_flow.py +++ b/homeassistant/components/bosch_alarm/config_flow.py @@ -11,7 +11,12 @@ from typing import Any from bosch_alarm_mode2 import Panel import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_RECONFIGURE, + SOURCE_USER, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import ( CONF_CODE, CONF_HOST, @@ -108,6 +113,13 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN): else: self._data = user_input self._data[CONF_MODEL] = model + + if self.source == SOURCE_RECONFIGURE: + if ( + self._get_reconfigure_entry().data[CONF_MODEL] + != self._data[CONF_MODEL] + ): + return self.async_abort(reason="device_mismatch") return await self.async_step_auth() return self.async_show_form( step_id="user", @@ -117,6 +129,12 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the reconfigure step.""" + return await self.async_step_user() + async def async_step_auth( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -154,10 +172,22 @@ class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN): else: if serial_number: await self.async_set_unique_id(str(serial_number)) - self._abort_if_unique_id_configured() - else: - self._async_abort_entries_match({CONF_HOST: self._data[CONF_HOST]}) - return self.async_create_entry(title=f"Bosch {model}", data=self._data) + if self.source == SOURCE_USER: + if serial_number: + self._abort_if_unique_id_configured() + else: + self._async_abort_entries_match( + {CONF_HOST: self._data[CONF_HOST]} + ) + return self.async_create_entry( + title=f"Bosch {model}", data=self._data + ) + if serial_number: + self._abort_if_unique_id_mismatch(reason="device_mismatch") + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data=self._data, + ) return self.async_show_form( step_id="auth", diff --git a/homeassistant/components/bosch_alarm/strings.json b/homeassistant/components/bosch_alarm/strings.json index 3123c1697f3..aad55eb04b1 100644 --- a/homeassistant/components/bosch_alarm/strings.json +++ b/homeassistant/components/bosch_alarm/strings.json @@ -43,7 +43,9 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", + "device_mismatch": "Please ensure you reconfigure against the same device." } }, "exceptions": { diff --git a/tests/components/bosch_alarm/test_config_flow.py b/tests/components/bosch_alarm/test_config_flow.py index 4a1c9dad3ea..9e79d1c1f5f 100644 --- a/tests/components/bosch_alarm/test_config_flow.py +++ b/tests/components/bosch_alarm/test_config_flow.py @@ -283,3 +283,85 @@ async def test_reauth_flow_error( assert result["reason"] == "reauth_successful" compare = {**mock_config_entry.data, **config_flow_data} assert compare == mock_config_entry.data + + +async def test_reconfig_flow( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, + mock_panel: AsyncMock, + model_name: str, + serial_number: str, + config_flow_data: dict[str, Any], +) -> None: + """Test reconfig auth.""" + await setup_integration(hass, mock_config_entry) + + config_flow_data = {k: f"{v}2" for k, v in config_flow_data.items()} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config_entry.entry_id, + }, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "1.1.1.1", CONF_PORT: 7700}, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "auth" + assert result["errors"] == {} + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + config_flow_data, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data == { + CONF_HOST: "1.1.1.1", + CONF_PORT: 7700, + CONF_MODEL: model_name, + **config_flow_data, + } + + +@pytest.mark.parametrize("model", ["b5512"]) +async def test_reconfig_flow_incorrect_model( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, + mock_panel: AsyncMock, + model_name: str, + serial_number: str, + config_flow_data: dict[str, Any], +) -> None: + """Test reconfig fails with a different device.""" + await setup_integration(hass, mock_config_entry) + + config_flow_data = {k: f"{v}2" for k, v in config_flow_data.items()} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config_entry.entry_id, + }, + ) + + mock_panel.model = "Solution 3000" + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "0.0.0.0", CONF_PORT: 7700}, + ) + + await hass.async_block_till_done() + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "device_mismatch"