Handle UpdateFailed for YouTube (#97233)

This commit is contained in:
Joost Lekkerkerker 2023-07-26 15:09:15 +02:00 committed by GitHub
parent db491c86c3
commit d233438e1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 22 deletions

View file

@ -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

View file

@ -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]
)

View file

@ -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

View file

@ -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

View file

@ -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"