mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Handle UpdateFailed for YouTube (#97233)
This commit is contained in:
parent
db491c86c3
commit
d233438e1a
@ -5,13 +5,13 @@ from datetime import timedelta
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from youtubeaio.helper import first
|
from youtubeaio.helper import first
|
||||||
from youtubeaio.types import UnauthorizedError
|
from youtubeaio.types import UnauthorizedError, YouTubeBackendError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_ICON, ATTR_ID
|
from homeassistant.const import ATTR_ICON, ATTR_ID
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from . import AsyncConfigEntryAuth
|
from . import AsyncConfigEntryAuth
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -70,4 +70,6 @@ class YouTubeDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
}
|
}
|
||||||
except UnauthorizedError as err:
|
except UnauthorizedError as err:
|
||||||
raise ConfigEntryAuthFailed from err
|
raise ConfigEntryAuthFailed from err
|
||||||
|
except YouTubeBackendError as err:
|
||||||
|
raise UpdateFailed("Couldn't connect to YouTube") from err
|
||||||
return res
|
return res
|
||||||
|
@ -87,9 +87,9 @@ class YouTubeSensor(YouTubeChannelEntity, SensorEntity):
|
|||||||
entity_description: YouTubeSensorEntityDescription
|
entity_description: YouTubeSensorEntityDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return if the entity is available."""
|
"""Return if the entity is available."""
|
||||||
return self.entity_description.available_fn(
|
return super().available and self.entity_description.available_fn(
|
||||||
self.coordinator.data[self._channel_id]
|
self.coordinator.data[self._channel_id]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from tests.common import load_fixture
|
|||||||
class MockYouTube:
|
class MockYouTube:
|
||||||
"""Service which returns mock objects."""
|
"""Service which returns mock objects."""
|
||||||
|
|
||||||
_authenticated = False
|
_thrown_error: Exception | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -28,7 +28,6 @@ class MockYouTube:
|
|||||||
self, token: str, scopes: list[AuthScope]
|
self, token: str, scopes: list[AuthScope]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Authenticate the user."""
|
"""Authenticate the user."""
|
||||||
self._authenticated = True
|
|
||||||
|
|
||||||
async def get_user_channels(self) -> AsyncGenerator[YouTubeChannel, None]:
|
async def get_user_channels(self) -> AsyncGenerator[YouTubeChannel, None]:
|
||||||
"""Get channels for authenticated user."""
|
"""Get channels for authenticated user."""
|
||||||
@ -40,6 +39,8 @@ class MockYouTube:
|
|||||||
self, channel_ids: list[str]
|
self, channel_ids: list[str]
|
||||||
) -> AsyncGenerator[YouTubeChannel, None]:
|
) -> AsyncGenerator[YouTubeChannel, None]:
|
||||||
"""Get channels."""
|
"""Get channels."""
|
||||||
|
if self._thrown_error is not None:
|
||||||
|
raise self._thrown_error
|
||||||
channels = json.loads(load_fixture(self._channel_fixture))
|
channels = json.loads(load_fixture(self._channel_fixture))
|
||||||
for item in channels["items"]:
|
for item in channels["items"]:
|
||||||
yield YouTubeChannel(**item)
|
yield YouTubeChannel(**item)
|
||||||
@ -57,3 +58,7 @@ class MockYouTube:
|
|||||||
channels = json.loads(load_fixture(self._subscriptions_fixture))
|
channels = json.loads(load_fixture(self._subscriptions_fixture))
|
||||||
for item in channels["items"]:
|
for item in channels["items"]:
|
||||||
yield YouTubeSubscription(**item)
|
yield YouTubeSubscription(**item)
|
||||||
|
|
||||||
|
def set_thrown_exception(self, exception: Exception) -> None:
|
||||||
|
"""Set thrown exception for testing purposes."""
|
||||||
|
self._thrown_error = exception
|
||||||
|
@ -18,7 +18,7 @@ from tests.common import MockConfigEntry
|
|||||||
from tests.components.youtube import MockYouTube
|
from tests.components.youtube import MockYouTube
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
ComponentSetup = Callable[[], Awaitable[None]]
|
ComponentSetup = Callable[[], Awaitable[MockYouTube]]
|
||||||
|
|
||||||
CLIENT_ID = "1234"
|
CLIENT_ID = "1234"
|
||||||
CLIENT_SECRET = "5678"
|
CLIENT_SECRET = "5678"
|
||||||
@ -92,7 +92,7 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
|
|||||||
@pytest.fixture(name="setup_integration")
|
@pytest.fixture(name="setup_integration")
|
||||||
async def mock_setup_integration(
|
async def mock_setup_integration(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||||
) -> Callable[[], Coroutine[Any, Any, None]]:
|
) -> Callable[[], Coroutine[Any, Any, MockYouTube]]:
|
||||||
"""Fixture for setting up the component."""
|
"""Fixture for setting up the component."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
@ -104,11 +104,11 @@ async def mock_setup_integration(
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def func() -> None:
|
async def func() -> MockYouTube:
|
||||||
with patch(
|
mock = MockYouTube()
|
||||||
"homeassistant.components.youtube.api.YouTube", return_value=MockYouTube()
|
with patch("homeassistant.components.youtube.api.YouTube", return_value=mock):
|
||||||
):
|
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
return mock
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
@ -3,12 +3,11 @@ from datetime import timedelta
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
from youtubeaio.types import UnauthorizedError
|
from youtubeaio.types import UnauthorizedError, YouTubeBackendError
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.youtube.const import DOMAIN
|
from homeassistant.components.youtube.const import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import MockYouTube
|
from . import MockYouTube
|
||||||
@ -87,11 +86,15 @@ async def test_sensor_reauth_trigger(
|
|||||||
hass: HomeAssistant, setup_integration: ComponentSetup
|
hass: HomeAssistant, setup_integration: ComponentSetup
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth is triggered after a refresh error."""
|
"""Test reauth is triggered after a refresh error."""
|
||||||
with patch(
|
mock = await setup_integration()
|
||||||
"youtubeaio.youtube.YouTube.get_channels", side_effect=UnauthorizedError
|
|
||||||
):
|
state = hass.states.get("sensor.google_for_developers_latest_upload")
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
assert state.state == "What's new in Google Home in less than 1 minute"
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
state = hass.states.get("sensor.google_for_developers_subscribers")
|
||||||
|
assert state.state == "2290000"
|
||||||
|
|
||||||
|
mock.set_thrown_exception(UnauthorizedError())
|
||||||
future = dt_util.utcnow() + timedelta(minutes=15)
|
future = dt_util.utcnow() + timedelta(minutes=15)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -103,3 +106,27 @@ async def test_sensor_reauth_trigger(
|
|||||||
assert flow["step_id"] == "reauth_confirm"
|
assert flow["step_id"] == "reauth_confirm"
|
||||||
assert flow["handler"] == DOMAIN
|
assert flow["handler"] == DOMAIN
|
||||||
assert flow["context"]["source"] == config_entries.SOURCE_REAUTH
|
assert flow["context"]["source"] == config_entries.SOURCE_REAUTH
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_unavailable(
|
||||||
|
hass: HomeAssistant, setup_integration: ComponentSetup
|
||||||
|
) -> None:
|
||||||
|
"""Test update failed."""
|
||||||
|
mock = await setup_integration()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.google_for_developers_latest_upload")
|
||||||
|
assert state.state == "What's new in Google Home in less than 1 minute"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.google_for_developers_subscribers")
|
||||||
|
assert state.state == "2290000"
|
||||||
|
|
||||||
|
mock.set_thrown_exception(YouTubeBackendError())
|
||||||
|
future = dt_util.utcnow() + timedelta(minutes=15)
|
||||||
|
async_fire_time_changed(hass, future)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.google_for_developers_latest_upload")
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.google_for_developers_subscribers")
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user