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,11 +643,16 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
playlists = soco.get_sonos_playlists(complete_result=True)
playlist = next((p for p in playlists if p.title == media_id), None)
if not playlist:
_LOGGER.error('Could not find a Sonos playlist named "%s"', media_id)
else:
soco.clear_queue()
soco.add_to_queue(playlist, timeout=LONG_SERVICE_TIMEOUT)
soco.play_from_queue(0)
raise ServiceValidationError(
translation_domain=SONOS_DOMAIN,
translation_key="invalid_sonos_playlist",
translation_placeholders={
"name": media_id,
},
)
soco.clear_queue()
soco.add_to_queue(playlist, timeout=LONG_SERVICE_TIMEOUT)
soco.play_from_queue(0)
elif media_type in PLAYABLE_MEDIA_TYPES:
item = media_browser.get_media(self.media.library, media_id, media_type)

View File

@ -177,6 +177,9 @@
"exceptions": {
"invalid_favorite": {
"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
from soco import SoCo
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 homeassistant.components import ssdp, zeroconf
@ -184,6 +184,7 @@ class SoCoMockFactory:
current_track_info_empty,
battery_info,
alarm_clock,
sonos_playlists: SearchResult,
) -> None:
"""Initialize the mock factory."""
self.mock_list: dict[str, MockSoCo] = {}
@ -192,6 +193,7 @@ class SoCoMockFactory:
self.current_track_info = current_track_info_empty
self.battery_info = battery_info
self.alarm_clock = alarm_clock
self.sonos_playlists = sonos_playlists
def cache_mock(
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.get_current_track_info.return_value = self.current_track_info
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["zone_name"] = name
my_speaker_info["uid"] = mock_soco.uid
@ -254,11 +257,21 @@ def soco_sharelink():
@pytest.fixture(name="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."""
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 (
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)
@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:
"""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."""
import logging
from typing import Any
from unittest.mock import patch
import pytest
from soco.data_structures import SearchResult
from syrupy import SnapshotAssertion
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.music_library.get_playlists.return_value = _mock_playlists
with caplog.at_level(logging.ERROR):
caplog.clear()
with pytest.raises(
ServiceValidationError,
match=f"Could not find Sonos playlist: {media_content_id}",
):
await hass.services.async_call(
MP_DOMAIN,
SERVICE_PLAY_MEDIA,
@ -548,8 +550,53 @@ async def test_play_media_music_library_playlist_dne(
blocking=True,
)
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(