mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Add exception handling to media source in Radio Browser integration (#164653)
This commit is contained in:
@@ -4,10 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import mimetypes
|
||||
|
||||
from aiodns.error import DNSError
|
||||
import pycountry
|
||||
from radios import FilterBy, Order, RadioBrowser, Station
|
||||
from radios import FilterBy, Order, RadioBrowser, RadioBrowserError, Station
|
||||
|
||||
from homeassistant.components.media_player import MediaClass, MediaType
|
||||
from homeassistant.components.media_player import BrowseError, MediaClass, MediaType
|
||||
from homeassistant.components.media_source import (
|
||||
BrowseMediaSource,
|
||||
MediaSource,
|
||||
@@ -15,6 +16,7 @@ from homeassistant.components.media_source import (
|
||||
PlayMedia,
|
||||
Unresolvable,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util.location import vincenty
|
||||
|
||||
@@ -55,9 +57,20 @@ class RadioMediaSource(MediaSource):
|
||||
|
||||
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
|
||||
"""Resolve selected Radio station to a streaming URL."""
|
||||
radios = self.radios
|
||||
|
||||
station = await radios.station(uuid=item.identifier)
|
||||
if self.entry.state != ConfigEntryState.LOADED:
|
||||
raise Unresolvable(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_not_ready",
|
||||
)
|
||||
radios = self.radios
|
||||
try:
|
||||
station = await radios.station(uuid=item.identifier)
|
||||
except (DNSError, RadioBrowserError) as e:
|
||||
raise Unresolvable(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="radio_browser_error",
|
||||
) from e
|
||||
if not station:
|
||||
raise Unresolvable("Radio station is no longer available")
|
||||
|
||||
@@ -74,25 +87,37 @@ class RadioMediaSource(MediaSource):
|
||||
item: MediaSourceItem,
|
||||
) -> BrowseMediaSource:
|
||||
"""Return media."""
|
||||
|
||||
if self.entry.state != ConfigEntryState.LOADED:
|
||||
raise BrowseError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_not_ready",
|
||||
)
|
||||
radios = self.radios
|
||||
|
||||
return BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=None,
|
||||
media_class=MediaClass.CHANNEL,
|
||||
media_content_type=MediaType.MUSIC,
|
||||
title=self.entry.title,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children_media_class=MediaClass.DIRECTORY,
|
||||
children=[
|
||||
*await self._async_build_popular(radios, item),
|
||||
*await self._async_build_by_tag(radios, item),
|
||||
*await self._async_build_by_language(radios, item),
|
||||
*await self._async_build_local(radios, item),
|
||||
*await self._async_build_by_country(radios, item),
|
||||
],
|
||||
)
|
||||
try:
|
||||
return BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=None,
|
||||
media_class=MediaClass.CHANNEL,
|
||||
media_content_type=MediaType.MUSIC,
|
||||
title=self.entry.title,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children_media_class=MediaClass.DIRECTORY,
|
||||
children=[
|
||||
*await self._async_build_popular(radios, item),
|
||||
*await self._async_build_by_tag(radios, item),
|
||||
*await self._async_build_by_language(radios, item),
|
||||
*await self._async_build_local(radios, item),
|
||||
*await self._async_build_by_country(radios, item),
|
||||
],
|
||||
)
|
||||
except (DNSError, RadioBrowserError) as e:
|
||||
raise BrowseError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="radio_browser_error",
|
||||
) from e
|
||||
|
||||
@callback
|
||||
@staticmethod
|
||||
|
||||
@@ -5,5 +5,13 @@
|
||||
"description": "Do you want to add Radio Browser to Home Assistant?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"config_entry_not_ready": {
|
||||
"message": "Radio Browser integration is not ready"
|
||||
},
|
||||
"radio_browser_error": {
|
||||
"message": "Error occurred while communicating with Radio Browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.radio_browser.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -39,10 +40,15 @@ async def init_integration(
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Radio Browser integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
with patch(
|
||||
"homeassistant.components.radio_browser.RadioBrowser",
|
||||
autospec=True,
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"""Tests for radio_browser media_source."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aiodns.error import DNSError
|
||||
import pytest
|
||||
from radios import FilterBy, Order
|
||||
from radios import FilterBy, Order, RadioBrowserError
|
||||
|
||||
from homeassistant.components import media_source
|
||||
from homeassistant.components.media_player import BrowseError
|
||||
from homeassistant.components.radio_browser.media_source import async_get_media_source
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
DOMAIN = "radio_browser"
|
||||
|
||||
|
||||
@@ -71,3 +76,113 @@ async def test_browsing_local(
|
||||
assert other_browse is not None
|
||||
assert other_browse.title == "My Radios"
|
||||
assert len(other_browse.children) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception",
|
||||
[DNSError, RadioBrowserError],
|
||||
)
|
||||
async def test_browsing_exceptions(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test browsing exceptions."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.radio_browser.RadioBrowser",
|
||||
autospec=True,
|
||||
) as mock_browser:
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
mock_browser.return_value.stations.side_effect = exception
|
||||
with pytest.raises(BrowseError) as exc_info:
|
||||
await media_source.async_browse_media(
|
||||
hass, f"{media_source.URI_SCHEME}{DOMAIN}/popular"
|
||||
)
|
||||
assert exc_info.value.translation_key == "radio_browser_error"
|
||||
|
||||
|
||||
async def test_browsing_not_ready(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browsing config entry not ready."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.radio_browser.RadioBrowser",
|
||||
autospec=True,
|
||||
) as mock_browser:
|
||||
mock_browser.return_value.stats.side_effect = RadioBrowserError
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
with pytest.raises(BrowseError) as exc_info:
|
||||
await media_source.async_browse_media(
|
||||
hass, f"{media_source.URI_SCHEME}{DOMAIN}/popular"
|
||||
)
|
||||
assert exc_info.value.translation_key == "config_entry_not_ready"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception",
|
||||
[DNSError, RadioBrowserError],
|
||||
)
|
||||
async def test_resolve_media_exceptions(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test resolving media exceptions."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.radio_browser.RadioBrowser",
|
||||
autospec=True,
|
||||
) as mock_browser:
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
mock_browser.return_value.station.side_effect = exception
|
||||
with pytest.raises(media_source.Unresolvable) as exc_info:
|
||||
await media_source.async_resolve_media(
|
||||
hass, f"{media_source.URI_SCHEME}{DOMAIN}/123456", None
|
||||
)
|
||||
assert exc_info.value.translation_key == "radio_browser_error"
|
||||
|
||||
|
||||
async def test_resolve_media_not_ready(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test resolving media config entry not ready."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.radio_browser.RadioBrowser",
|
||||
autospec=True,
|
||||
) as mock_browser:
|
||||
mock_browser.return_value.stats.side_effect = RadioBrowserError
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
with pytest.raises(media_source.Unresolvable) as exc_info:
|
||||
await media_source.async_resolve_media(
|
||||
hass, f"{media_source.URI_SCHEME}{DOMAIN}/123456", None
|
||||
)
|
||||
assert exc_info.value.translation_key == "config_entry_not_ready"
|
||||
|
||||
Reference in New Issue
Block a user