Fix Sonos select_source timeout error (#115640)
parent
b5cd0e629d
commit
731fe17224
|
@ -39,7 +39,7 @@ from homeassistant.components.plex.services import process_plex_payload
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TIME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform, service
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
@ -432,7 +432,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||
fav = [fav for fav in self.speaker.favorites if fav.title == name]
|
||||
|
||||
if len(fav) != 1:
|
||||
return
|
||||
raise ServiceValidationError(
|
||||
translation_domain=SONOS_DOMAIN,
|
||||
translation_key="invalid_favorite",
|
||||
translation_placeholders={
|
||||
"name": name,
|
||||
},
|
||||
)
|
||||
|
||||
src = fav.pop()
|
||||
self._play_favorite(src)
|
||||
|
@ -445,7 +451,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||
MUSIC_SRC_RADIO,
|
||||
MUSIC_SRC_LINE_IN,
|
||||
]:
|
||||
soco.play_uri(uri, title=favorite.title)
|
||||
soco.play_uri(uri, title=favorite.title, timeout=LONG_SERVICE_TIMEOUT)
|
||||
else:
|
||||
soco.clear_queue()
|
||||
soco.add_to_queue(favorite.reference, timeout=LONG_SERVICE_TIMEOUT)
|
||||
|
|
|
@ -173,5 +173,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"invalid_favorite": {
|
||||
"message": "Could not find a Sonos favorite: {name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|||
import pytest
|
||||
from soco import SoCo
|
||||
from soco.alarms import Alarms
|
||||
from soco.data_structures import DidlFavorite, SearchResult
|
||||
from soco.events_base import Event as SonosEvent
|
||||
|
||||
from homeassistant.components import ssdp, zeroconf
|
||||
|
@ -17,7 +18,7 @@ from homeassistant.components.sonos import DOMAIN
|
|||
from homeassistant.const import CONF_HOSTS
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.common import MockConfigEntry, load_fixture, load_json_value_fixture
|
||||
|
||||
|
||||
class SonosMockEventListener:
|
||||
|
@ -304,6 +305,14 @@ def config_fixture():
|
|||
return {DOMAIN: {MP_DOMAIN: {CONF_HOSTS: ["192.168.42.2"]}}}
|
||||
|
||||
|
||||
@pytest.fixture(name="sonos_favorites")
|
||||
def sonos_favorites_fixture() -> SearchResult:
|
||||
"""Create sonos favorites fixture."""
|
||||
favorites = load_json_value_fixture("sonos_favorites.json", "sonos")
|
||||
favorite_list = [DidlFavorite.from_dict(fav) for fav in favorites]
|
||||
return SearchResult(favorite_list, "favorites", 3, 3, 1)
|
||||
|
||||
|
||||
class MockMusicServiceItem:
|
||||
"""Mocks a Soco MusicServiceItem."""
|
||||
|
||||
|
@ -408,10 +417,10 @@ def mock_get_music_library_information(
|
|||
|
||||
|
||||
@pytest.fixture(name="music_library")
|
||||
def music_library_fixture():
|
||||
def music_library_fixture(sonos_favorites: SearchResult) -> Mock:
|
||||
"""Create music_library fixture."""
|
||||
music_library = MagicMock()
|
||||
music_library.get_sonos_favorites.return_value.update_id = 1
|
||||
music_library.get_sonos_favorites.return_value = sonos_favorites
|
||||
music_library.browse_by_idstring = mock_browse_by_idstring
|
||||
music_library.get_music_library_information = mock_get_music_library_information
|
||||
return music_library
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
[
|
||||
{
|
||||
"title": "66 - Watercolors",
|
||||
"parent_id": "FV:2",
|
||||
"item_id": "FV:2/4",
|
||||
"resource_meta_data": "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"><item id=\"10090120Api%3atune%3aliveAudio%3ajazzcafe%3ae4b5402c-9999-9999-9999-4bc8e2cdccce\" parentID=\"10086064live%3f93b0b9cb-9999-9999-9999-bcf75971fcfe\" restricted=\"false\"><dc:title>66 - Watercolors</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">SA_RINCON9479_X_#Svc9479-99999999-Token</desc></item></DIDL-Lite>",
|
||||
"resources": [
|
||||
{
|
||||
"uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc",
|
||||
"protocol_info": "a:b:c:d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "James Taylor Radio",
|
||||
"parent_id": "FV:2",
|
||||
"item_id": "FV:2/13",
|
||||
"resource_meta_data": "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"><item id=\"100c2068ST%3a1683194971234567890\" parentID=\"10fe2064myStations\" restricted=\"true\"><dc:title>James Taylor Radio</dc:title><upnp:class>object.item.audioItem.audioBroadcast.#station</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">SA_RINCON60423_X_#Svc60423-99999999-Token</desc></item></DIDL-Lite>",
|
||||
"resources": [
|
||||
{
|
||||
"uri": "x-sonosapi-radio:ST%3aetc",
|
||||
"protocol_info": "a:b:c:d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "1984",
|
||||
"parent_id": "FV:2",
|
||||
"item_id": "FV:2/8",
|
||||
"resource_meta_data": "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"><item id=\"A:ALBUMARTIST/Aerosmith/1984\" parentID=\"A:ALBUMARTIST/Aerosmith\" restricted=\"true\"><dc:title>1984</dc:title><upnp:class>object.container.album.musicAlbum</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>",
|
||||
"resources": [
|
||||
{
|
||||
"uri": "x-rincon-playlist:RINCON_test#A:ALBUMARTIST/Aerosmith/1984",
|
||||
"protocol_info": "a:b:c:d"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
"""Tests for the Sonos Media Player platform."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -9,10 +10,15 @@ from homeassistant.components.media_player import (
|
|||
SERVICE_PLAY_MEDIA,
|
||||
MediaPlayerEnqueue,
|
||||
)
|
||||
from homeassistant.components.media_player.const import ATTR_MEDIA_ENQUEUE
|
||||
from homeassistant.components.media_player.const import (
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
)
|
||||
from homeassistant.components.sonos.const import SOURCE_LINEIN, SOURCE_TV
|
||||
from homeassistant.components.sonos.media_player import LONG_SERVICE_TIMEOUT
|
||||
from homeassistant.const import STATE_IDLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
CONNECTION_UPNP,
|
||||
|
@ -272,3 +278,154 @@ async def test_play_media_music_library_playlist_dne(
|
|||
assert soco_mock.play_uri.call_count == 0
|
||||
assert media_content_id in caplog.text
|
||||
assert "playlist" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("source", "result"),
|
||||
[
|
||||
(
|
||||
SOURCE_LINEIN,
|
||||
{
|
||||
"switch_to_line_in": 1,
|
||||
},
|
||||
),
|
||||
(
|
||||
SOURCE_TV,
|
||||
{
|
||||
"switch_to_tv": 1,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_select_source_line_in_tv(
|
||||
hass: HomeAssistant,
|
||||
soco_factory: SoCoMockFactory,
|
||||
async_autosetup_sonos,
|
||||
source: str,
|
||||
result: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test the select_source method with a variety of inputs."""
|
||||
soco_mock = soco_factory.mock_list.get("192.168.42.2")
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
"entity_id": "media_player.zone_a",
|
||||
"source": source,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert soco_mock.switch_to_line_in.call_count == result.get("switch_to_line_in", 0)
|
||||
assert soco_mock.switch_to_tv.call_count == result.get("switch_to_tv", 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("source", "result"),
|
||||
[
|
||||
(
|
||||
"James Taylor Radio",
|
||||
{
|
||||
"play_uri": 1,
|
||||
"play_uri_uri": "x-sonosapi-radio:ST%3aetc",
|
||||
"play_uri_title": "James Taylor Radio",
|
||||
},
|
||||
),
|
||||
(
|
||||
"66 - Watercolors",
|
||||
{
|
||||
"play_uri": 1,
|
||||
"play_uri_uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc",
|
||||
"play_uri_title": "66 - Watercolors",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_select_source_play_uri(
|
||||
hass: HomeAssistant,
|
||||
soco_factory: SoCoMockFactory,
|
||||
async_autosetup_sonos,
|
||||
source: str,
|
||||
result: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test the select_source method with a variety of inputs."""
|
||||
soco_mock = soco_factory.mock_list.get("192.168.42.2")
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
"entity_id": "media_player.zone_a",
|
||||
"source": source,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert soco_mock.play_uri.call_count == result.get("play_uri")
|
||||
soco_mock.play_uri.assert_called_with(
|
||||
result.get("play_uri_uri"),
|
||||
title=result.get("play_uri_title"),
|
||||
timeout=LONG_SERVICE_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("source", "result"),
|
||||
[
|
||||
(
|
||||
"1984",
|
||||
{
|
||||
"add_to_queue": 1,
|
||||
"add_to_queue_item_id": "A:ALBUMARTIST/Aerosmith/1984",
|
||||
"clear_queue": 1,
|
||||
"play_from_queue": 1,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_select_source_play_queue(
|
||||
hass: HomeAssistant,
|
||||
soco_factory: SoCoMockFactory,
|
||||
async_autosetup_sonos,
|
||||
source: str,
|
||||
result: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test the select_source method with a variety of inputs."""
|
||||
soco_mock = soco_factory.mock_list.get("192.168.42.2")
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
"entity_id": "media_player.zone_a",
|
||||
"source": source,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert soco_mock.clear_queue.call_count == result.get("clear_queue")
|
||||
assert soco_mock.add_to_queue.call_count == result.get("add_to_queue")
|
||||
assert soco_mock.add_to_queue.call_args_list[0].args[0].item_id == result.get(
|
||||
"add_to_queue_item_id"
|
||||
)
|
||||
assert (
|
||||
soco_mock.add_to_queue.call_args_list[0].kwargs["timeout"]
|
||||
== LONG_SERVICE_TIMEOUT
|
||||
)
|
||||
assert soco_mock.play_from_queue.call_count == result.get("play_from_queue")
|
||||
soco_mock.play_from_queue.assert_called_with(0)
|
||||
|
||||
|
||||
async def test_select_source_error(
|
||||
hass: HomeAssistant,
|
||||
soco_factory: SoCoMockFactory,
|
||||
async_autosetup_sonos,
|
||||
) -> None:
|
||||
"""Test the select_source method with a variety of inputs."""
|
||||
with pytest.raises(ServiceValidationError) as sve:
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
"entity_id": "media_player.zone_a",
|
||||
"source": "invalid_source",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert "invalid_source" in str(sve.value)
|
||||
assert "Could not find a Sonos favorite" in str(sve.value)
|
||||
|
|
Loading…
Reference in New Issue