mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 16:05:13 +00:00
Refactor Xbox integration setup and exception handling (#154823)
This commit is contained in:
@@ -4,15 +4,10 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from xbox.webapi.api.client import XboxLiveClient
|
||||
from xbox.webapi.api.provider.smartglass.models import SmartglassConsoleList
|
||||
from xbox.webapi.common.signed_session import SignedSession
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import api
|
||||
from .const import DOMAIN
|
||||
from .coordinator import XboxConfigEntry, XboxUpdateCoordinator
|
||||
|
||||
@@ -30,24 +25,8 @@ PLATFORMS = [
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: XboxConfigEntry) -> bool:
|
||||
"""Set up xbox from a config entry."""
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
)
|
||||
)
|
||||
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||
signed_session = await hass.async_add_executor_job(SignedSession)
|
||||
auth = api.AsyncConfigEntryAuth(signed_session, session)
|
||||
|
||||
client = XboxLiveClient(auth)
|
||||
consoles: SmartglassConsoleList = await client.smartglass.get_console_list()
|
||||
_LOGGER.debug(
|
||||
"Found %d consoles: %s",
|
||||
len(consoles.result),
|
||||
consoles.model_dump(),
|
||||
)
|
||||
|
||||
coordinator = XboxUpdateCoordinator(hass, entry, client, consoles)
|
||||
coordinator = XboxUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from httpx import HTTPStatusError, RequestError, TimeoutException
|
||||
from xbox.webapi.api.client import XboxLiveClient
|
||||
from xbox.webapi.api.provider.catalog.const import SYSTEM_PFN_ID_MAP
|
||||
from xbox.webapi.api.provider.catalog.models import AlternateIdType, Product
|
||||
@@ -18,12 +19,15 @@ from xbox.webapi.api.provider.smartglass.models import (
|
||||
SmartglassConsoleList,
|
||||
SmartglassConsoleStatus,
|
||||
)
|
||||
from xbox.webapi.common.signed_session import SignedSession
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from . import api
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -60,21 +64,21 @@ class PresenceData:
|
||||
class XboxData:
|
||||
"""Xbox dataclass for update coordinator."""
|
||||
|
||||
consoles: dict[str, ConsoleData]
|
||||
presence: dict[str, PresenceData]
|
||||
consoles: dict[str, ConsoleData] = field(default_factory=dict)
|
||||
presence: dict[str, PresenceData] = field(default_factory=dict)
|
||||
|
||||
|
||||
class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
"""Store Xbox Console Status."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
consoles: SmartglassConsoleList
|
||||
client: XboxLiveClient
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
client: XboxLiveClient,
|
||||
consoles: SmartglassConsoleList,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
@@ -84,11 +88,52 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=10),
|
||||
)
|
||||
self.data = XboxData({}, {})
|
||||
self.client: XboxLiveClient = client
|
||||
self.consoles: SmartglassConsoleList = consoles
|
||||
self.data = XboxData()
|
||||
self.current_friends: set[str] = set()
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up coordinator."""
|
||||
try:
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
self.hass, self.config_entry
|
||||
)
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_exception",
|
||||
translation_placeholders={"error": str(e)},
|
||||
) from e
|
||||
|
||||
session = config_entry_oauth2_flow.OAuth2Session(
|
||||
self.hass, self.config_entry, implementation
|
||||
)
|
||||
signed_session = await self.hass.async_add_executor_job(SignedSession)
|
||||
auth = api.AsyncConfigEntryAuth(signed_session, session)
|
||||
self.client = XboxLiveClient(auth)
|
||||
|
||||
try:
|
||||
self.consoles = await self.client.smartglass.get_console_list()
|
||||
except TimeoutException as e:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="timeout_exception",
|
||||
) from e
|
||||
except (RequestError, HTTPStatusError) as e:
|
||||
_LOGGER.debug("Xbox exception:", exc_info=True)
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_exception",
|
||||
translation_placeholders={"error": str(e)},
|
||||
) from e
|
||||
|
||||
_LOGGER.debug(
|
||||
"Found %d consoles: %s",
|
||||
len(self.consoles.result),
|
||||
self.consoles.model_dump(),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> XboxData:
|
||||
"""Fetch the latest console status."""
|
||||
# Update Console Status
|
||||
|
||||
@@ -51,5 +51,13 @@
|
||||
"name": "In multiplayer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"request_exception": {
|
||||
"message": "Failed to connect Xbox Network: {error}"
|
||||
},
|
||||
"timeout_exception": {
|
||||
"message": "Failed to connect Xbox Network due to a connection timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ async def setup_credentials(hass: HomeAssistant) -> None:
|
||||
def mock_oauth2_implementation() -> Generator[AsyncMock]:
|
||||
"""Mock config entry oauth2 implementation."""
|
||||
with patch(
|
||||
"homeassistant.components.xbox.config_entry_oauth2_flow.async_get_config_entry_implementation",
|
||||
"homeassistant.components.xbox.coordinator.config_entry_oauth2_flow.async_get_config_entry_implementation",
|
||||
return_value=AsyncMock(),
|
||||
) as mock_client:
|
||||
client = mock_client.return_value
|
||||
@@ -89,7 +89,7 @@ def mock_signed_session() -> Generator[AsyncMock]:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.xbox.SignedSession", autospec=True
|
||||
"homeassistant.components.xbox.coordinator.SignedSession", autospec=True
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.xbox.config_flow.SignedSession", new=mock_client
|
||||
@@ -106,7 +106,7 @@ def mock_xbox_live_client(signed_session) -> Generator[AsyncMock]:
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.xbox.XboxLiveClient", autospec=True
|
||||
"homeassistant.components.xbox.coordinator.XboxLiveClient", autospec=True
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.xbox.config_flow.XboxLiveClient", new=mock_client
|
||||
|
||||
66
tests/components/xbox/test_init.py
Normal file
66
tests/components/xbox/test_init.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Tests for the Xbox integration."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from httpx import ConnectTimeout, HTTPStatusError, ProtocolError
|
||||
import pytest
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("xbox_live_client")
|
||||
async def test_entry_setup_unload(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test integration setup and unload."""
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception",
|
||||
[ConnectTimeout, HTTPStatusError, ProtocolError],
|
||||
)
|
||||
async def test_config_entry_not_ready(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
xbox_live_client: AsyncMock,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test config entry not ready."""
|
||||
|
||||
xbox_live_client.smartglass.get_console_list.side_effect = exception
|
||||
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.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("xbox_live_client")
|
||||
async def test_config_implementation_not_available(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test implementation not available."""
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.xbox.coordinator.config_entry_oauth2_flow.async_get_config_entry_implementation",
|
||||
side_effect=ValueError("Implementation not available"),
|
||||
):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
Reference in New Issue
Block a user