From 1ad2e4951da2d32be036bcdd8a1eece94de65ab5 Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Fri, 24 May 2024 04:42:45 -0400 Subject: [PATCH] Fix Sonos album artwork performance (#116391) --- .../components/sonos/media_browser.py | 14 +- tests/components/sonos/conftest.py | 43 +++++- .../sonos/fixtures/music_library_albums.json | 23 +++ .../fixtures/music_library_categories.json | 44 ++++++ .../sonos/fixtures/music_library_tracks.json | 14 ++ .../sonos/snapshots/test_media_browser.ambr | 133 ++++++++++++++++++ tests/components/sonos/test_media_browser.py | 82 +++++++++++ 7 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 tests/components/sonos/fixtures/music_library_albums.json create mode 100644 tests/components/sonos/fixtures/music_library_categories.json create mode 100644 tests/components/sonos/fixtures/music_library_tracks.json create mode 100644 tests/components/sonos/snapshots/test_media_browser.ambr diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 498607c5465..3416896e879 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -53,14 +53,16 @@ def get_thumbnail_url_full( media_content_type: str, media_content_id: str, media_image_id: str | None = None, + item: MusicServiceItem | None = None, ) -> str | None: """Get thumbnail URL.""" if is_internal: - item = get_media( - media.library, - media_content_id, - media_content_type, - ) + if not item: + item = get_media( + media.library, + media_content_id, + media_content_type, + ) return urllib.parse.unquote(getattr(item, "album_art_uri", "")) return urllib.parse.unquote( @@ -255,7 +257,7 @@ def item_payload(item: DidlObject, get_thumbnail_url=None) -> BrowseMedia: content_id = get_content_id(item) thumbnail = None if getattr(item, "album_art_uri", None): - thumbnail = get_thumbnail_url(media_class, content_id) + thumbnail = get_thumbnail_url(media_class, content_id, item=item) return BrowseMedia( title=item.title, diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 465ac6e2728..657813b303f 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -316,12 +316,35 @@ def sonos_favorites_fixture() -> SearchResult: class MockMusicServiceItem: """Mocks a Soco MusicServiceItem.""" - def __init__(self, title: str, item_id: str, parent_id: str, item_class: str): + def __init__( + self, + title: str, + item_id: str, + parent_id: str, + item_class: str, + album_art_uri: None | str = None, + ): """Initialize the mock item.""" self.title = title self.item_id = item_id self.item_class = item_class self.parent_id = parent_id + self.album_art_uri: None | str = album_art_uri + + +def list_from_json_fixture(file_name: str) -> list[MockMusicServiceItem]: + """Create a list of music service items from a json fixture file.""" + item_list = load_json_value_fixture(file_name, "sonos") + return [ + MockMusicServiceItem( + item.get("title"), + item.get("item_id"), + item.get("parent_id"), + item.get("item_class"), + item.get("album_art_uri"), + ) + for item in item_list + ] def mock_browse_by_idstring( @@ -398,6 +421,10 @@ def mock_browse_by_idstring( "object.container.album.musicAlbum", ), ] + if search_type == "tracks": + return list_from_json_fixture("music_library_tracks.json") + if search_type == "albums" and idstring == "A:ALBUM": + return list_from_json_fixture("music_library_albums.json") return [] @@ -416,13 +443,23 @@ def mock_get_music_library_information( ] +@pytest.fixture(name="music_library_browse_categories") +def music_library_browse_categories() -> list[MockMusicServiceItem]: + """Create fixture for top-level music library categories.""" + return list_from_json_fixture("music_library_categories.json") + + @pytest.fixture(name="music_library") -def music_library_fixture(sonos_favorites: SearchResult) -> Mock: +def music_library_fixture( + sonos_favorites: SearchResult, + music_library_browse_categories: list[MockMusicServiceItem], +) -> Mock: """Create music_library fixture.""" music_library = MagicMock() music_library.get_sonos_favorites.return_value = sonos_favorites - music_library.browse_by_idstring = mock_browse_by_idstring + music_library.browse_by_idstring = Mock(side_effect=mock_browse_by_idstring) music_library.get_music_library_information = mock_get_music_library_information + music_library.browse = Mock(return_value=music_library_browse_categories) return music_library diff --git a/tests/components/sonos/fixtures/music_library_albums.json b/tests/components/sonos/fixtures/music_library_albums.json new file mode 100644 index 00000000000..4941abe8ba7 --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_albums.json @@ -0,0 +1,23 @@ +[ + { + "title": "A Hard Day's Night", + "item_id": "A:ALBUM/A%20Hard%20Day's%20Night", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fThe%2520Beatles%2fA%2520Hard%2520Day's%2520Night%2f01%2520A%2520Hard%2520Day's%2520Night%25201.m4a&v=53" + }, + { + "title": "Abbey Road", + "item_id": "A:ALBUM/Abbey%20Road", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fThe%2520Beatles%2fAbbeyA%2520Road%2f01%2520Come%2520Together.m4a&v=53" + }, + { + "title": "Between Good And Evil", + "item_id": "A:ALBUM/Between%20Good%20And%20Evil", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fSantana%2fA%2520Between%2520Good%2520And%2520Evil%2f02%2520A%2520Persuasion.m4a&v=53" + } +] diff --git a/tests/components/sonos/fixtures/music_library_categories.json b/tests/components/sonos/fixtures/music_library_categories.json new file mode 100644 index 00000000000..b6d6d3bf2dd --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_categories.json @@ -0,0 +1,44 @@ +[ + { + "title": "Contributing Artists", + "item_id": "A:ARTIST", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Artists", + "item_id": "A:ALBUMARTIST", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Albums", + "item_id": "A:ALBUM", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Genres", + "item_id": "A:GENRE", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Composers", + "item_id": "A:COMPOSER", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Tracks", + "item_id": "A:TRACKS", + "parent_id": "A:", + "item_class": "object.container.playlistContainer" + }, + { + "title": "Playlists", + "item_id": "A:PLAYLISTS", + "parent_id": "A:", + "item_class": "object.container" + } +] diff --git a/tests/components/sonos/fixtures/music_library_tracks.json b/tests/components/sonos/fixtures/music_library_tracks.json new file mode 100644 index 00000000000..1f1fcdbc21c --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_tracks.json @@ -0,0 +1,14 @@ +[ + { + "title": "A Hard Day's Night", + "item_id": "S://192.168.42.100/music/iTunes/The%20Beatles/A%20Hard%20Day%2fs%20Night/A%20Hard%20Day%2fs%20Night.mp3", + "parent_id": "A:ALBUMARTIST/Beatles/A%20Hard%20Day's%20Night", + "item_class": "object.container.album.musicTrack" + }, + { + "title": "I Should Have Known Better", + "item_id": "S://192.168.42.100/music/iTunes/The%20Beatles/A%20Hard%20Day%2fs%I%20Should%20Have%20Known%20Better.mp3", + "parent_id": "A:ALBUMARTIST/Beatles/A%20Hard%20Day's%20Night", + "item_class": "object.container.album.musicTrack" + } +] diff --git a/tests/components/sonos/snapshots/test_media_browser.ambr b/tests/components/sonos/snapshots/test_media_browser.ambr new file mode 100644 index 00000000000..b4388b148e5 --- /dev/null +++ b/tests/components/sonos/snapshots/test_media_browser.ambr @@ -0,0 +1,133 @@ +# serializer version: 1 +# name: test_browse_media_library + list([ + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'contributing_artist', + 'media_content_id': 'A:ARTIST', + 'media_content_type': 'contributing_artist', + 'thumbnail': None, + 'title': 'Contributing Artists', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'artist', + 'media_content_id': 'A:ALBUMARTIST', + 'media_content_type': 'artist', + 'thumbnail': None, + 'title': 'Artists', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM', + 'media_content_type': 'album', + 'thumbnail': None, + 'title': 'Albums', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'genre', + 'media_content_id': 'A:GENRE', + 'media_content_type': 'genre', + 'thumbnail': None, + 'title': 'Genres', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'composer', + 'media_content_id': 'A:COMPOSER', + 'media_content_type': 'composer', + 'thumbnail': None, + 'title': 'Composers', + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'track', + 'media_content_id': 'A:TRACKS', + 'media_content_type': 'track', + 'thumbnail': None, + 'title': 'Tracks', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'playlist', + 'media_content_id': 'A:PLAYLISTS', + 'media_content_type': 'playlist', + 'thumbnail': None, + 'title': 'Playlists', + }), + ]) +# --- +# name: test_browse_media_library_albums + list([ + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': "A:ALBUM/A%20Hard%20Day's%20Night", + 'media_content_type': 'album', + 'thumbnail': "http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/The%20Beatles/A%20Hard%20Day's%20Night/01%20A%20Hard%20Day's%20Night%201.m4a&v=53", + 'title': "A Hard Day's Night", + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM/Abbey%20Road', + 'media_content_type': 'album', + 'thumbnail': 'http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/The%20Beatles/AbbeyA%20Road/01%20Come%20Together.m4a&v=53', + 'title': 'Abbey Road', + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM/Between%20Good%20And%20Evil', + 'media_content_type': 'album', + 'thumbnail': 'http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/Santana/A%20Between%20Good%20And%20Evil/02%20A%20Persuasion.m4a&v=53', + 'title': 'Between Good And Evil', + }), + ]) +# --- +# name: test_browse_media_root + list([ + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'directory', + 'media_content_id': '', + 'media_content_type': 'favorites', + 'thumbnail': 'https://brands.home-assistant.io/_/sonos/logo.png', + 'title': 'Favorites', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'directory', + 'media_content_id': '', + 'media_content_type': 'library', + 'thumbnail': 'https://brands.home-assistant.io/_/sonos/logo.png', + 'title': 'Music Library', + }), + ]) +# --- diff --git a/tests/components/sonos/test_media_browser.py b/tests/components/sonos/test_media_browser.py index d8d0e1c3a07..4f6c2f53d8b 100644 --- a/tests/components/sonos/test_media_browser.py +++ b/tests/components/sonos/test_media_browser.py @@ -2,6 +2,8 @@ from functools import partial +from syrupy import SnapshotAssertion + from homeassistant.components.media_player.browse_media import BrowseMedia from homeassistant.components.media_player.const import MediaClass, MediaType from homeassistant.components.sonos.media_browser import ( @@ -12,6 +14,8 @@ from homeassistant.core import HomeAssistant from .conftest import SoCoMockFactory +from tests.typing import WebSocketGenerator + class MockMusicServiceItem: """Mocks a Soco MusicServiceItem.""" @@ -95,3 +99,81 @@ async def test_build_item_response( browse_item.children[1].media_content_id == "x-file-cifs://192.168.42.10/music/The%20Beatles/Abbey%20Road/03%20Something.mp3" ) + + +async def test_browse_media_root( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + + +async def test_browse_media_library( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + "media_content_id": "", + "media_content_type": "library", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + + +async def test_browse_media_library_albums( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + "media_content_id": "A:ALBUM", + "media_content_type": "album", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + assert soco_mock.music_library.browse_by_idstring.call_count == 1