diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index 8831895c71e..95fec101c9d 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -53,6 +53,12 @@ DATA_SCHEMA = vol.Schema( }, extra=vol.REMOVE_EXTRA, ) +RECONFIGURE_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): str, + }, + extra=vol.REMOVE_EXTRA, +) CAPTCHA_SCHEMA = vol.Schema( { vol.Required(CONF_CAPTCHA_TOKEN): str, @@ -111,9 +117,8 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN): unique_id = f"{user_input[CONF_REGION]}-{user_input[CONF_USERNAME]}" await self.async_set_unique_id(unique_id) - if self.source in {SOURCE_REAUTH, SOURCE_RECONFIGURE}: - self._abort_if_unique_id_mismatch(reason="account_mismatch") - else: + # Unique ID cannot change for reauth/reconfigure + if self.source not in {SOURCE_REAUTH, SOURCE_RECONFIGURE}: self._abort_if_unique_id_configured() # Store user input for later use @@ -166,19 +171,39 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + async def async_step_change_password( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Show the change password step.""" + existing_data = ( + dict(self._existing_entry_data) if self._existing_entry_data else {} + ) + + if user_input is not None: + return await self.async_step_user(existing_data | user_input) + + return self.async_show_form( + step_id="change_password", + data_schema=RECONFIGURE_SCHEMA, + description_placeholders={ + CONF_USERNAME: existing_data[CONF_USERNAME], + CONF_REGION: existing_data[CONF_REGION], + }, + ) + async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._existing_entry_data = entry_data - return await self.async_step_user() + return await self.async_step_change_password() async def async_step_reconfigure( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a reconfiguration flow initialized by the user.""" self._existing_entry_data = self._get_reconfigure_entry().data - return await self.async_step_user() + return await self.async_step_change_password() async def async_step_captcha( self, user_input: dict[str, Any] | None = None diff --git a/homeassistant/components/bmw_connected_drive/strings.json b/homeassistant/components/bmw_connected_drive/strings.json index 8078971acd1..93abce5d73f 100644 --- a/homeassistant/components/bmw_connected_drive/strings.json +++ b/homeassistant/components/bmw_connected_drive/strings.json @@ -2,6 +2,7 @@ "config": { "step": { "user": { + "description": "Enter your MyBMW/MINI Connected credentials.", "data": { "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", @@ -17,6 +18,12 @@ "data_description": { "captcha_token": "One-time token retrieved from the captcha challenge." } + }, + "change_password": { + "description": "Update your MyBMW/MINI Connected password for account `{username}` in region `{region}`.", + "data": { + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { @@ -27,8 +34,7 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", - "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", - "account_mismatch": "Username and region are not allowed to change" + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, "options": { diff --git a/tests/components/bmw_connected_drive/test_config_flow.py b/tests/components/bmw_connected_drive/test_config_flow.py index 8fa9d9be22b..9c124261392 100644 --- a/tests/components/bmw_connected_drive/test_config_flow.py +++ b/tests/components/bmw_connected_drive/test_config_flow.py @@ -15,7 +15,7 @@ from homeassistant.components.bmw_connected_drive.const import ( CONF_READ_ONLY, CONF_REFRESH_TOKEN, ) -from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -224,19 +224,11 @@ async def test_reauth(hass: HomeAssistant) -> None: result = await config_entry.start_reauth_flow(hass) assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - suggested_values = { - key: key.description.get("suggested_value") - for key in result["data_schema"].schema - } - assert suggested_values[CONF_USERNAME] == FIXTURE_USER_INPUT[CONF_USERNAME] - assert suggested_values[CONF_PASSWORD] == wrong_password - assert suggested_values[CONF_REGION] == FIXTURE_USER_INPUT[CONF_REGION] + assert result["step_id"] == "change_password" + assert set(result["data_schema"].schema) == {CONF_PASSWORD} result = await hass.config_entries.flow.async_configure( - result["flow_id"], deepcopy(FIXTURE_USER_INPUT) + result["flow_id"], {CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD]} ) await hass.async_block_till_done() @@ -254,41 +246,6 @@ async def test_reauth(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 2 -async def test_reauth_unique_id_abort(hass: HomeAssistant) -> None: - """Test aborting the reauth form if unique_id changes.""" - with patch( - "bimmer_connected.api.authentication.MyBMWAuthentication.login", - side_effect=login_sideeffect, - autospec=True, - ): - wrong_password = "wrong" - - config_entry_with_wrong_password = deepcopy(FIXTURE_CONFIG_ENTRY) - config_entry_with_wrong_password["data"][CONF_PASSWORD] = wrong_password - - config_entry = MockConfigEntry(**config_entry_with_wrong_password) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.data == config_entry_with_wrong_password["data"] - - result = await config_entry.start_reauth_flow(hass) - 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"], {**FIXTURE_USER_INPUT, CONF_REGION: "north_america"} - ) - await hass.async_block_till_done() - - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "account_mismatch" - assert config_entry.data == config_entry_with_wrong_password["data"] - - async def test_reconfigure(hass: HomeAssistant) -> None: """Test the reconfiguration form.""" with patch( @@ -304,19 +261,11 @@ async def test_reconfigure(hass: HomeAssistant) -> None: result = await config_entry.start_reconfigure_flow(hass) assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {} - - suggested_values = { - key: key.description.get("suggested_value") - for key in result["data_schema"].schema - } - assert suggested_values[CONF_USERNAME] == FIXTURE_USER_INPUT[CONF_USERNAME] - assert suggested_values[CONF_PASSWORD] == FIXTURE_USER_INPUT[CONF_PASSWORD] - assert suggested_values[CONF_REGION] == FIXTURE_USER_INPUT[CONF_REGION] + assert result["step_id"] == "change_password" + assert set(result["data_schema"].schema) == {CONF_PASSWORD} result = await hass.config_entries.flow.async_configure( - result["flow_id"], FIXTURE_USER_INPUT + result["flow_id"], {CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD]} ) await hass.async_block_till_done() @@ -330,32 +279,3 @@ async def test_reconfigure(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reconfigure_successful" assert config_entry.data == FIXTURE_COMPLETE_ENTRY - - -async def test_reconfigure_unique_id_abort(hass: HomeAssistant) -> None: - """Test aborting the reconfiguration form if unique_id changes.""" - with patch( - "bimmer_connected.api.authentication.MyBMWAuthentication.login", - side_effect=login_sideeffect, - autospec=True, - ): - config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) - config_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - result = await config_entry.start_reconfigure_flow(hass) - 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"], - {**FIXTURE_USER_INPUT, CONF_USERNAME: "somebody@email.com"}, - ) - await hass.async_block_till_done() - - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "account_mismatch" - assert config_entry.data == FIXTURE_COMPLETE_ENTRY