Add Sonos tests for media_player play Sonos Playlist and improve error handling (#124126)

* initial commit

* initial commit

* initial commit

* updates

* add json fixture

* use match on pytest.raises
This commit is contained in:
Pete Sage 2024-08-20 12:38:04 -04:00 committed by GitHub
parent b74aced6f3
commit d327ec904c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 102 additions and 13 deletions

View File

@ -643,8 +643,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
playlists = soco.get_sonos_playlists(complete_result=True) playlists = soco.get_sonos_playlists(complete_result=True)
playlist = next((p for p in playlists if p.title == media_id), None) playlist = next((p for p in playlists if p.title == media_id), None)
if not playlist: if not playlist:
_LOGGER.error('Could not find a Sonos playlist named "%s"', media_id) raise ServiceValidationError(
else: translation_domain=SONOS_DOMAIN,
translation_key="invalid_sonos_playlist",
translation_placeholders={
"name": media_id,
},
)
soco.clear_queue() soco.clear_queue()
soco.add_to_queue(playlist, timeout=LONG_SERVICE_TIMEOUT) soco.add_to_queue(playlist, timeout=LONG_SERVICE_TIMEOUT)
soco.play_from_queue(0) soco.play_from_queue(0)

View File

@ -177,6 +177,9 @@
"exceptions": { "exceptions": {
"invalid_favorite": { "invalid_favorite": {
"message": "Could not find a Sonos favorite: {name}" "message": "Could not find a Sonos favorite: {name}"
},
"invalid_sonos_playlist": {
"message": "Could not find Sonos playlist: {name}"
} }
} }
} }

View File

@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest import pytest
from soco import SoCo from soco import SoCo
from soco.alarms import Alarms from soco.alarms import Alarms
from soco.data_structures import DidlFavorite, SearchResult from soco.data_structures import DidlFavorite, DidlPlaylistContainer, SearchResult
from soco.events_base import Event as SonosEvent from soco.events_base import Event as SonosEvent
from homeassistant.components import ssdp, zeroconf from homeassistant.components import ssdp, zeroconf
@ -184,6 +184,7 @@ class SoCoMockFactory:
current_track_info_empty, current_track_info_empty,
battery_info, battery_info,
alarm_clock, alarm_clock,
sonos_playlists: SearchResult,
) -> None: ) -> None:
"""Initialize the mock factory.""" """Initialize the mock factory."""
self.mock_list: dict[str, MockSoCo] = {} self.mock_list: dict[str, MockSoCo] = {}
@ -192,6 +193,7 @@ class SoCoMockFactory:
self.current_track_info = current_track_info_empty self.current_track_info = current_track_info_empty
self.battery_info = battery_info self.battery_info = battery_info
self.alarm_clock = alarm_clock self.alarm_clock = alarm_clock
self.sonos_playlists = sonos_playlists
def cache_mock( def cache_mock(
self, mock_soco: MockSoCo, ip_address: str, name: str = "Zone A" self, mock_soco: MockSoCo, ip_address: str, name: str = "Zone A"
@ -204,6 +206,7 @@ class SoCoMockFactory:
mock_soco.music_library = self.music_library mock_soco.music_library = self.music_library
mock_soco.get_current_track_info.return_value = self.current_track_info mock_soco.get_current_track_info.return_value = self.current_track_info
mock_soco.music_source_from_uri = SoCo.music_source_from_uri mock_soco.music_source_from_uri = SoCo.music_source_from_uri
mock_soco.get_sonos_playlists.return_value = self.sonos_playlists
my_speaker_info = self.speaker_info.copy() my_speaker_info = self.speaker_info.copy()
my_speaker_info["zone_name"] = name my_speaker_info["zone_name"] = name
my_speaker_info["uid"] = mock_soco.uid my_speaker_info["uid"] = mock_soco.uid
@ -254,11 +257,21 @@ def soco_sharelink():
@pytest.fixture(name="soco_factory") @pytest.fixture(name="soco_factory")
def soco_factory( def soco_factory(
music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock music_library,
speaker_info,
current_track_info_empty,
battery_info,
alarm_clock,
sonos_playlists: SearchResult,
): ):
"""Create factory for instantiating SoCo mocks.""" """Create factory for instantiating SoCo mocks."""
factory = SoCoMockFactory( factory = SoCoMockFactory(
music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock music_library,
speaker_info,
current_track_info_empty,
battery_info,
alarm_clock,
sonos_playlists,
) )
with ( with (
patch("homeassistant.components.sonos.SoCo", new=factory.get_mock), patch("homeassistant.components.sonos.SoCo", new=factory.get_mock),
@ -335,6 +348,14 @@ def sonos_favorites_fixture() -> SearchResult:
return SearchResult(favorite_list, "favorites", 3, 3, 1) return SearchResult(favorite_list, "favorites", 3, 3, 1)
@pytest.fixture(name="sonos_playlists")
def sonos_playlists_fixture() -> SearchResult:
"""Create sonos playlist fixture."""
playlists = load_json_value_fixture("sonos_playlists.json", "sonos")
playlists_list = [DidlPlaylistContainer.from_dict(pl) for pl in playlists]
return SearchResult(playlists_list, "sonos_playlists", 1, 1, 0)
class MockMusicServiceItem: class MockMusicServiceItem:
"""Mocks a Soco MusicServiceItem.""" """Mocks a Soco MusicServiceItem."""

View File

@ -0,0 +1,13 @@
[
{
"title": "sample playlist",
"parent_id": "SQ:",
"item_id": "SQ:0",
"resources": [
{
"uri": "file:///jffs/settings/savedqueues.rsq#0",
"protocol_info": "file:*:audio/mpegurl:*"
}
]
}
]

View File

@ -1,10 +1,10 @@
"""Tests for the Sonos Media Player platform.""" """Tests for the Sonos Media Player platform."""
import logging
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from soco.data_structures import SearchResult
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
@ -535,8 +535,10 @@ async def test_play_media_music_library_playlist_dne(
soco_mock = soco_factory.mock_list.get("192.168.42.2") soco_mock = soco_factory.mock_list.get("192.168.42.2")
soco_mock.music_library.get_playlists.return_value = _mock_playlists soco_mock.music_library.get_playlists.return_value = _mock_playlists
with caplog.at_level(logging.ERROR): with pytest.raises(
caplog.clear() ServiceValidationError,
match=f"Could not find Sonos playlist: {media_content_id}",
):
await hass.services.async_call( await hass.services.async_call(
MP_DOMAIN, MP_DOMAIN,
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
@ -548,8 +550,53 @@ async def test_play_media_music_library_playlist_dne(
blocking=True, blocking=True,
) )
assert soco_mock.play_uri.call_count == 0 assert soco_mock.play_uri.call_count == 0
assert media_content_id in caplog.text
assert "playlist" in caplog.text
async def test_play_sonos_playlist(
hass: HomeAssistant,
async_autosetup_sonos,
soco: MockSoCo,
sonos_playlists: SearchResult,
) -> None:
"""Test that sonos playlists can be played."""
# Test a successful call
await hass.services.async_call(
MP_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.zone_a",
ATTR_MEDIA_CONTENT_TYPE: "playlist",
ATTR_MEDIA_CONTENT_ID: "sample playlist",
},
blocking=True,
)
assert soco.clear_queue.call_count == 1
assert soco.add_to_queue.call_count == 1
soco.add_to_queue.asset_called_with(
sonos_playlists[0], timeout=LONG_SERVICE_TIMEOUT
)
# Test playing a non-existent playlist
soco.clear_queue.reset_mock()
soco.add_to_queue.reset_mock()
media_content_id: str = "bad playlist"
with pytest.raises(
ServiceValidationError,
match=f"Could not find Sonos playlist: {media_content_id}",
):
await hass.services.async_call(
MP_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.zone_a",
ATTR_MEDIA_CONTENT_TYPE: "playlist",
ATTR_MEDIA_CONTENT_ID: media_content_id,
},
blocking=True,
)
assert soco.clear_queue.call_count == 0
assert soco.add_to_queue.call_count == 0
@pytest.mark.parametrize( @pytest.mark.parametrize(