diff --git a/homeassistant/components/xbox/config_flow.py b/homeassistant/components/xbox/config_flow.py index a7e6d3a9ff6..6f95ff31434 100644 --- a/homeassistant/components/xbox/config_flow.py +++ b/homeassistant/components/xbox/config_flow.py @@ -38,10 +38,6 @@ class OAuth2FlowHandler( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow start.""" - - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - return await super().async_step_user(user_input) async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: @@ -57,4 +53,5 @@ class OAuth2FlowHandler( me = await client.people.get_friends_by_xuid(client.xuid) await self.async_set_unique_id(client.xuid) + self._abort_if_unique_id_configured() return self.async_create_entry(title=me.people[0].gamertag, data=data) diff --git a/homeassistant/components/xbox/coordinator.py b/homeassistant/components/xbox/coordinator.py index f6f27d34543..c6fd566c326 100644 --- a/homeassistant/components/xbox/coordinator.py +++ b/homeassistant/components/xbox/coordinator.py @@ -199,8 +199,13 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]): ) from e else: presence_data = {self.client.xuid: batch.people[0]} + configured_xuids = self.configured_as_entry() presence_data.update( - {friend.xuid: friend for friend in friends.people if friend.is_favorite} + { + friend.xuid: friend + for friend in friends.people + if friend.is_favorite and friend.xuid not in configured_xuids + } ) # retrieve title details @@ -252,9 +257,11 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]): """Remove stale devices from registry.""" device_reg = dr.async_get(self.hass) - identifiers = {(DOMAIN, xuid) for xuid in xuids} | { - (DOMAIN, console.id) for console in self.consoles.result - } + identifiers = ( + {(DOMAIN, xuid) for xuid in xuids} + | {(DOMAIN, console.id) for console in self.consoles.result} + | self.configured_as_entry() + ) for device in dr.async_entries_for_config_entry( device_reg, self.config_entry.entry_id @@ -264,3 +271,12 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]): device_reg.async_update_device( device.id, remove_config_entry_id=self.config_entry.entry_id ) + + def configured_as_entry(self) -> set[str]: + """Get xuids of configured entries.""" + + return { + entry.unique_id + for entry in self.hass.config_entries.async_entries(DOMAIN) + if entry.unique_id is not None + } diff --git a/homeassistant/components/xbox/strings.json b/homeassistant/components/xbox/strings.json index 208fc7d8740..adb0fb6fcc0 100644 --- a/homeassistant/components/xbox/strings.json +++ b/homeassistant/components/xbox/strings.json @@ -1,13 +1,13 @@ { "config": { "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]", "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]", - "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]" }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" diff --git a/tests/components/xbox/conftest.py b/tests/components/xbox/conftest.py index 15d7d0b7e94..cbbc04a130f 100644 --- a/tests/components/xbox/conftest.py +++ b/tests/components/xbox/conftest.py @@ -67,6 +67,7 @@ def mock_config_entry() -> MockConfigEntry: "user_id": "AAAAAAAAAAAAAAAAAAAAA", }, }, + unique_id="271958441785640", ) diff --git a/tests/components/xbox/test_config_flow.py b/tests/components/xbox/test_config_flow.py index 66c92d7e807..dc85544c0b2 100644 --- a/tests/components/xbox/test_config_flow.py +++ b/tests/components/xbox/test_config_flow.py @@ -18,17 +18,6 @@ from tests.test_util.aiohttp import AiohttpClientMocker from tests.typing import ClientSessionGenerator -async def test_abort_if_existing_entry(hass: HomeAssistant) -> None: - """Check flow abort when an entry already exist.""" - MockConfigEntry(domain=DOMAIN).add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - "xbox", context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "single_instance_allowed" - - @pytest.mark.usefixtures( "current_request_with_host", "xbox_live_client", @@ -90,6 +79,58 @@ async def test_full_flow( assert len(mock_setup.mock_calls) == 1 +@pytest.mark.usefixtures( + "current_request_with_host", + "xbox_live_client", + "authentication_manager", +) +async def test_form_already_configured( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + config_entry: MockConfigEntry, +) -> None: + """Test we abort flow when entry is already configured.""" + + config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + 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 == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "scope": "XboxLive.signin XboxLive.offline_access", + "service": "xbox", + "token_type": "bearer", + "user_id": "AAAAAAAAAAAAAAAAAAAAA", + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + @pytest.mark.usefixtures("xbox_live_client") async def test_unique_id_migration( hass: HomeAssistant,