Handle UpdateFailed for YouTube (#97233)
This commit is contained in:
parent
db491c86c3
commit
d233438e1a
5 changed files with 56 additions and 22 deletions
|
@ -5,13 +5,13 @@ from datetime import timedelta
|
|||
from typing import Any
|
||||
|
||||
from youtubeaio.helper import first
|
||||
from youtubeaio.types import UnauthorizedError
|
||||
from youtubeaio.types import UnauthorizedError, YouTubeBackendError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ICON, ATTR_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from . import AsyncConfigEntryAuth
|
||||
from .const import (
|
||||
|
@ -70,4 +70,6 @@ class YouTubeDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||
}
|
||||
except UnauthorizedError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except YouTubeBackendError as err:
|
||||
raise UpdateFailed("Couldn't connect to YouTube") from err
|
||||
return res
|
||||
|
|
|
@ -87,9 +87,9 @@ class YouTubeSensor(YouTubeChannelEntity, SensorEntity):
|
|||
entity_description: YouTubeSensorEntityDescription
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""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]
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from tests.common import load_fixture
|
|||
class MockYouTube:
|
||||
"""Service which returns mock objects."""
|
||||
|
||||
_authenticated = False
|
||||
_thrown_error: Exception | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -28,7 +28,6 @@ class MockYouTube:
|
|||
self, token: str, scopes: list[AuthScope]
|
||||
) -> None:
|
||||
"""Authenticate the user."""
|
||||
self._authenticated = True
|
||||
|
||||
async def get_user_channels(self) -> AsyncGenerator[YouTubeChannel, None]:
|
||||
"""Get channels for authenticated user."""
|
||||
|
@ -40,6 +39,8 @@ class MockYouTube:
|
|||
self, channel_ids: list[str]
|
||||
) -> AsyncGenerator[YouTubeChannel, None]:
|
||||
"""Get channels."""
|
||||
if self._thrown_error is not None:
|
||||
raise self._thrown_error
|
||||
channels = json.loads(load_fixture(self._channel_fixture))
|
||||
for item in channels["items"]:
|
||||
yield YouTubeChannel(**item)
|
||||
|
@ -57,3 +58,7 @@ class MockYouTube:
|
|||
channels = json.loads(load_fixture(self._subscriptions_fixture))
|
||||
for item in channels["items"]:
|
||||
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.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
ComponentSetup = Callable[[], Awaitable[None]]
|
||||
ComponentSetup = Callable[[], Awaitable[MockYouTube]]
|
||||
|
||||
CLIENT_ID = "1234"
|
||||
CLIENT_SECRET = "5678"
|
||||
|
@ -92,7 +92,7 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
|
|||
@pytest.fixture(name="setup_integration")
|
||||
async def mock_setup_integration(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> Callable[[], Coroutine[Any, Any, None]]:
|
||||
) -> Callable[[], Coroutine[Any, Any, MockYouTube]]:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
|
@ -104,11 +104,11 @@ async def mock_setup_integration(
|
|||
DOMAIN,
|
||||
)
|
||||
|
||||
async def func() -> None:
|
||||
with patch(
|
||||
"homeassistant.components.youtube.api.YouTube", return_value=MockYouTube()
|
||||
):
|
||||
async def func() -> MockYouTube:
|
||||
mock = MockYouTube()
|
||||
with patch("homeassistant.components.youtube.api.YouTube", return_value=mock):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
return mock
|
||||
|
||||
return func
|
||||
|
|
|
@ -3,12 +3,11 @@ from datetime import timedelta
|
|||
from unittest.mock import patch
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
from youtubeaio.types import UnauthorizedError
|
||||
from youtubeaio.types import UnauthorizedError, YouTubeBackendError
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.youtube.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import MockYouTube
|
||||
|
@ -87,14 +86,18 @@ async def test_sensor_reauth_trigger(
|
|||
hass: HomeAssistant, setup_integration: ComponentSetup
|
||||
) -> None:
|
||||
"""Test reauth is triggered after a refresh error."""
|
||||
with patch(
|
||||
"youtubeaio.youtube.YouTube.get_channels", side_effect=UnauthorizedError
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
future = dt_util.utcnow() + timedelta(minutes=15)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
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(UnauthorizedError())
|
||||
future = dt_util.utcnow() + timedelta(minutes=15)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
|
||||
|
@ -103,3 +106,27 @@ async def test_sensor_reauth_trigger(
|
|||
assert flow["step_id"] == "reauth_confirm"
|
||||
assert flow["handler"] == DOMAIN
|
||||
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…
Add table
Reference in a new issue