diff --git a/homeassistant/components/dropbox/config_flow.py b/homeassistant/components/dropbox/config_flow.py index 23dc3bb2d833..819f9eb5bca5 100644 --- a/homeassistant/components/dropbox/config_flow.py +++ b/homeassistant/components/dropbox/config_flow.py @@ -6,7 +6,11 @@ from typing import Any, override from python_dropbox_api import DropboxAPIClient -from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_REAUTH, + SOURCE_RECONFIGURE, + ConfigFlowResult, +) from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler @@ -46,16 +50,25 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN): account_info = await client.get_account_info() await self.async_set_unique_id(account_info.account_id) - if self.source == SOURCE_REAUTH: - self._abort_if_unique_id_mismatch(reason="wrong_account") - return self.async_update_reload_and_abort( - self._get_reauth_entry(), data=data + if self.source in (SOURCE_REAUTH, SOURCE_RECONFIGURE): + entry = ( + self._get_reauth_entry() + if self.source == SOURCE_REAUTH + else self._get_reconfigure_entry() ) + self._abort_if_unique_id_mismatch(reason="wrong_account") + return self.async_update_reload_and_abort(entry, data=data) self._abort_if_unique_id_configured() return self.async_create_entry(title=account_info.email, data=data) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfiguration flow.""" + return await self.async_step_user(user_input) + async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: diff --git a/homeassistant/components/dropbox/quality_scale.yaml b/homeassistant/components/dropbox/quality_scale.yaml index b86b63aad42f..0ba2ad1e85bb 100644 --- a/homeassistant/components/dropbox/quality_scale.yaml +++ b/homeassistant/components/dropbox/quality_scale.yaml @@ -106,7 +106,7 @@ rules: icon-translations: status: exempt comment: Integration does not have any entities. - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: Integration does not have any repairs. diff --git a/homeassistant/components/dropbox/strings.json b/homeassistant/components/dropbox/strings.json index 31be06e8838a..d57567d093f7 100644 --- a/homeassistant/components/dropbox/strings.json +++ b/homeassistant/components/dropbox/strings.json @@ -11,6 +11,7 @@ "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]", "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]", "wrong_account": "Wrong account: Please authenticate with the correct account." }, diff --git a/tests/components/dropbox/test_config_flow.py b/tests/components/dropbox/test_config_flow.py index 9b53612bcbeb..32e13a0890a7 100644 --- a/tests/components/dropbox/test_config_flow.py +++ b/tests/components/dropbox/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Dropbox config flow.""" from types import SimpleNamespace -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock import pytest from yarl import URL @@ -19,6 +19,7 @@ from homeassistant.helpers import config_entry_oauth2_flow from .conftest import ACCOUNT_EMAIL, ACCOUNT_ID, CLIENT_ID +from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker from tests.typing import ClientSessionGenerator @@ -254,3 +255,82 @@ async def test_reauth_flow( assert mock_setup_entry.await_count == expected_setup_calls assert mock_config_entry.data["token"]["access_token"] == expected_access_token + + +@pytest.mark.usefixtures("current_request_with_host") +@pytest.mark.parametrize( + ( + "new_account_info", + "expected_reason", + "expected_setup_calls", + "expected_access_token", + ), + [ + ( + SimpleNamespace(account_id=ACCOUNT_ID, email=ACCOUNT_EMAIL), + "reconfigure_successful", + 1, + "updated-access-token", + ), + ( + SimpleNamespace(account_id="dbid:different", email="other@example.com"), + "wrong_account", + 0, + "mock-access-token", + ), + ], + ids=["success", "wrong_account"], +) +async def test_reconfigure_flow( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, + mock_dropbox_client: MagicMock, + mock_setup_entry: AsyncMock, + new_account_info: SimpleNamespace, + expected_reason: str, + expected_setup_calls: int, + expected_access_token: str, +) -> None: + """Test reconfiguration flow outcomes.""" + + mock_config_entry.add_to_hass(hass) + + mock_dropbox_client.get_account_info.return_value = new_account_info + + result = await mock_config_entry.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.EXTERNAL_STEP + assert result["step_id"] == "auth" + + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "updated-access-token", + "token_type": "Bearer", + "expires_in": 120, + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == expected_reason + assert mock_setup_entry.await_count == expected_setup_calls + + assert mock_config_entry.data["token"]["access_token"] == expected_access_token