From c97be4d0d18d986ed98b3f26dec63184a4502c08 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 9 Oct 2024 12:09:42 +0200 Subject: [PATCH] Add Spotify media player tests (#127839) * Improve Spotify mock * Add Spotify media player tests * Remove extra changes * Remove available markets * Remove available markets * Fix tests --- tests/components/spotify/conftest.py | 8 +- .../components/spotify/fixtures/playback.json | 106 ++++ .../spotify/fixtures/playback_episode.json | 110 ++++ .../components/spotify/fixtures/playlist.json | 520 ++++++++++++++++++ tests/components/spotify/test_media_player.py | 172 ++++++ 5 files changed, 914 insertions(+), 2 deletions(-) create mode 100644 tests/components/spotify/fixtures/playback.json create mode 100644 tests/components/spotify/fixtures/playback_episode.json create mode 100644 tests/components/spotify/fixtures/playlist.json create mode 100644 tests/components/spotify/test_media_player.py diff --git a/tests/components/spotify/conftest.py b/tests/components/spotify/conftest.py index 0adeb63c8a5..d0cf754fc56 100644 --- a/tests/components/spotify/conftest.py +++ b/tests/components/spotify/conftest.py @@ -76,7 +76,11 @@ def mock_spotify() -> Generator[MagicMock]: client.current_user_playlists.return_value = load_json_value_fixture( "current_user_playlist.json", DOMAIN ) - client.current_user.return_value = load_json_value_fixture( - "current_user.json", DOMAIN + current_user = load_json_value_fixture("current_user.json", DOMAIN) + client.current_user.return_value = current_user + client.me.return_value = current_user + client.current_playback.return_value = load_json_value_fixture( + "playback.json", DOMAIN ) + client.playlist.return_value = load_json_value_fixture("playlist.json", DOMAIN) yield spotify_mock diff --git a/tests/components/spotify/fixtures/playback.json b/tests/components/spotify/fixtures/playback.json new file mode 100644 index 00000000000..d0bf8e0478a --- /dev/null +++ b/tests/components/spotify/fixtures/playback.json @@ -0,0 +1,106 @@ +{ + "device": { + "id": "a19f7a03a25aff3e43f457a328a8ba67a8c44789", + "is_active": true, + "is_private_session": false, + "is_restricted": false, + "name": "Master Bathroom Speaker", + "type": "Speaker", + "volume_percent": 25 + }, + "shuffle_state": false, + "repeat_state": "off", + "timestamp": 1689639030791, + "context": { + "external_urls": { + "spotify": "https://open.spotify.com/playlist/2r35vbe6hHl6yDSMfjKgmm" + }, + "href": "https://api.spotify.com/v1/playlists/2r35vbe6hHl6yDSMfjKgmm", + "type": "playlist", + "uri": "spotify:user:rushofficial:playlist:2r35vbe6hHl6yDSMfjKgmm" + }, + "progress_ms": 249367, + "item": { + "album": { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2Hkut4rAAyrQxRdof7FVJq" + }, + "href": "https://api.spotify.com/v1/artists/2Hkut4rAAyrQxRdof7FVJq", + "id": "2Hkut4rAAyrQxRdof7FVJq", + "name": "Rush", + "type": "artist", + "uri": "spotify:artist:2Hkut4rAAyrQxRdof7FVJq" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/3nUNxSh2szhmN7iifAKv5i" + }, + "href": "https://api.spotify.com/v1/albums/3nUNxSh2szhmN7iifAKv5i", + "id": "3nUNxSh2szhmN7iifAKv5i", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b27306c0d7ebcabad0c39b566983", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e0206c0d7ebcabad0c39b566983", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d0000485106c0d7ebcabad0c39b566983", + "width": 64 + } + ], + "name": "Permanent Waves", + "release_date": "1980-01-01", + "release_date_precision": "day", + "total_tracks": 6, + "type": "album", + "uri": "spotify:album:3nUNxSh2szhmN7iifAKv5i" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2Hkut4rAAyrQxRdof7FVJq" + }, + "href": "https://api.spotify.com/v1/artists/2Hkut4rAAyrQxRdof7FVJq", + "id": "2Hkut4rAAyrQxRdof7FVJq", + "name": "Rush", + "type": "artist", + "uri": "spotify:artist:2Hkut4rAAyrQxRdof7FVJq" + } + ], + "disc_number": 1, + "duration_ms": 296466, + "explicit": false, + "external_ids": { + "isrc": "USMR18070028" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/4e9hUiLsN4mx61ARosFi7p" + }, + "href": "https://api.spotify.com/v1/tracks/4e9hUiLsN4mx61ARosFi7p", + "id": "4e9hUiLsN4mx61ARosFi7p", + "is_local": false, + "name": "The Spirit Of Radio", + "popularity": 68, + "preview_url": "https://p.scdn.co/mp3-preview/75cc52f458b2416f33f15c499783c51119ba9a93?cid=20bbc62823a3412ba5267ea5398e52d0", + "track_number": 1, + "type": "track", + "uri": "spotify:track:4e9hUiLsN4mx61ARosFi7p" + }, + "currently_playing_type": "track", + "actions": { + "disallows": { + "skipping_prev": true, + "toggling_repeat_track": true + } + }, + "is_playing": true +} diff --git a/tests/components/spotify/fixtures/playback_episode.json b/tests/components/spotify/fixtures/playback_episode.json new file mode 100644 index 00000000000..2030d6499ed --- /dev/null +++ b/tests/components/spotify/fixtures/playback_episode.json @@ -0,0 +1,110 @@ +{ + "device": { + "id": null, + "is_active": true, + "is_private_session": false, + "is_restricted": true, + "name": "Sonos Roam SL", + "supports_volume": true, + "type": "Speaker", + "volume_percent": 46 + }, + "shuffle_state": false, + "smart_shuffle": false, + "repeat_state": "off", + "timestamp": 1728219605131, + "context": { + "external_urls": { + "spotify": "https://open.spotify.com/show/1Y9ExMgMxoBVrgrfU7u0nD" + }, + "href": "https://api.spotify.com/v1/shows/1Y9ExMgMxoBVrgrfU7u0nD", + "type": "show", + "uri": "spotify:show:1Y9ExMgMxoBVrgrfU7u0nD" + }, + "progress_ms": 5410, + "item": { + "audio_preview_url": "https://podz-content.spotifycdn.com/audio/clips/06lRxUmh8UNVTByuyxLYqh/clip_132296_192296.mp3", + "description": "Patreon: https://www.patreon.com/safetythirdMerch: https://safetythird.shopYouTube: https://www.youtube.com/@safetythird/Advertising Inquiries: https://redcircle.com/brandsPrivacy & Opt-Out: https://redcircle.com/privacy", + "duration_ms": 3690161, + "explicit": false, + "external_urls": { + "spotify": "https://open.spotify.com/episode/3o0RYoo5iOMKSmEbunsbvW" + }, + "href": "https://api.spotify.com/v1/episodes/3o0RYoo5iOMKSmEbunsbvW", + "html_description": "

Patreon: https://www.patreon.com/safetythird

Merch: https://safetythird.shop

YouTube: https://www.youtube.com/@safetythird/



Advertising Inquiries: https://redcircle.com/brands

Privacy & Opt-Out: https://redcircle.com/privacy", + "id": "3o0RYoo5iOMKSmEbunsbvW", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ac7bedd27a4413b1abf926d8a", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fc7bedd27a4413b1abf926d8a", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dc7bedd27a4413b1abf926d8a", + "width": 64 + } + ], + "is_externally_hosted": false, + "is_playable": true, + "language": "en-US", + "languages": ["en-US"], + "name": "My Squirrel Has Brain Damage - Safety Third 119", + "release_date": "2024-07-26", + "release_date_precision": "day", + "resume_point": { + "fully_played": false, + "resume_position_ms": 0 + }, + "show": { + "copyrights": [], + "description": "Safety Third is a weekly show hosted by William Osman, NileRed, The Backyard Scientist, Allen Pan, and a couple other YouTube \"Scientists\". Sometimes we have guests, sometimes it's just us, but always: safety is our number three priority.", + "explicit": true, + "external_urls": { + "spotify": "https://open.spotify.com/show/1Y9ExMgMxoBVrgrfU7u0nD" + }, + "href": "https://api.spotify.com/v1/shows/1Y9ExMgMxoBVrgrfU7u0nD", + "html_description": "

Safety Third is a weekly show hosted by William Osman, NileRed, The Backyard Scientist, Allen Pan, and a couple other YouTube "Scientists". Sometimes we have guests, sometimes it's just us, but always: safety is our number three priority.

", + "id": "1Y9ExMgMxoBVrgrfU7u0nD", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab6765630000ba8ac7bedd27a4413b1abf926d8a", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67656300005f1fc7bedd27a4413b1abf926d8a", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab6765630000f68dc7bedd27a4413b1abf926d8a", + "width": 64 + } + ], + "is_externally_hosted": false, + "languages": ["en-US"], + "media_type": "audio", + "name": "Safety Third", + "publisher": "Safety Third ", + "total_episodes": 120, + "type": "show", + "uri": "spotify:show:1Y9ExMgMxoBVrgrfU7u0nD" + }, + "type": "episode", + "uri": "spotify:episode:3o0RYoo5iOMKSmEbunsbvW" + }, + "currently_playing_type": "episode", + "actions": { + "disallows": { + "resuming": true + } + }, + "is_playing": true +} diff --git a/tests/components/spotify/fixtures/playlist.json b/tests/components/spotify/fixtures/playlist.json new file mode 100644 index 00000000000..36c28cc814b --- /dev/null +++ b/tests/components/spotify/fixtures/playlist.json @@ -0,0 +1,520 @@ +{ + "collaborative": false, + "external_urls": { + "spotify": "https://open.spotify.com/playlist/3cEYpjA9oz9GiPac4AsH4n" + }, + "followers": { + "href": null, + "total": 562 + }, + "href": "https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n?locale=en-US%2Cen%3Bq%3D0.5", + "id": "3cEYpjA9oz9GiPac4AsH4n", + "images": [ + { + "url": "https://i.scdn.co/image/ab67706c0000da848d0ce13d55f634e290f744ba", + "height": null, + "width": null + } + ], + "primary_color": null, + "name": "Spotify Web API Testing playlist", + "description": "A playlist for testing pourposes", + "type": "playlist", + "uri": "spotify:playlist:3cEYpjA9oz9GiPac4AsH4n", + "owner": { + "href": "https://api.spotify.com/v1/users/jmperezperez", + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "display_name": "JMPerez²", + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + } + }, + "public": true, + "snapshot_id": "MTgsZWFmNmZiNTIzYTg4ODM0OGQzZWQzOGI4NTdkNTJlMjU0OWFkYTUxMA==", + "tracks": { + "limit": 100, + "next": null, + "offset": 0, + "previous": null, + "href": "https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n/tracks?offset=0&limit=100&locale=en-US%2Cen%3Bq%3D0.5", + "total": 5, + "items": [ + { + "added_at": "2015-01-15T12:39:22Z", + "primary_color": null, + "video_thumbnail": { + "url": null + }, + "is_local": false, + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + }, + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "href": "https://api.spotify.com/v1/users/jmperezperez" + }, + "track": { + "preview_url": "https://p.scdn.co/mp3-preview/04599a1fe12ffac01d2bcb08340f84c0dd2cc335?cid=c7c59b798aab4892ac040a25f7dd1575", + "explicit": false, + "type": "track", + "episode": false, + "track": true, + "album": { + "type": "album", + "album_type": "compilation", + "href": "https://api.spotify.com/v1/albums/2pANdqPvxInB0YvcDiw4ko", + "id": "2pANdqPvxInB0YvcDiw4ko", + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b273ce6d0eef0c1ce77e5f95bbbc", + "width": 640, + "height": 640 + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e02ce6d0eef0c1ce77e5f95bbbc", + "width": 300, + "height": 300 + }, + { + "url": "https://i.scdn.co/image/ab67616d00004851ce6d0eef0c1ce77e5f95bbbc", + "width": 64, + "height": 64 + } + ], + "name": "Progressive Psy Trance Picks Vol.8", + "release_date": "2012-04-02", + "release_date_precision": "day", + "uri": "spotify:album:2pANdqPvxInB0YvcDiw4ko", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/0LyfQWJT6nXafLPZqxe9Of" + }, + "href": "https://api.spotify.com/v1/artists/0LyfQWJT6nXafLPZqxe9Of", + "id": "0LyfQWJT6nXafLPZqxe9Of", + "name": "Various Artists", + "type": "artist", + "uri": "spotify:artist:0LyfQWJT6nXafLPZqxe9Of" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/2pANdqPvxInB0YvcDiw4ko" + }, + "total_tracks": 20 + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/6eSdhw46riw2OUHgMwR8B5" + }, + "href": "https://api.spotify.com/v1/artists/6eSdhw46riw2OUHgMwR8B5", + "id": "6eSdhw46riw2OUHgMwR8B5", + "name": "Odiseo", + "type": "artist", + "uri": "spotify:artist:6eSdhw46riw2OUHgMwR8B5" + } + ], + "disc_number": 1, + "track_number": 10, + "duration_ms": 376000, + "external_ids": { + "isrc": "DEKC41200989" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/4rzfv0JLZfVhOhbSQ8o5jZ" + }, + "href": "https://api.spotify.com/v1/tracks/4rzfv0JLZfVhOhbSQ8o5jZ", + "id": "4rzfv0JLZfVhOhbSQ8o5jZ", + "name": "Api", + "popularity": 2, + "uri": "spotify:track:4rzfv0JLZfVhOhbSQ8o5jZ", + "is_local": false + } + }, + { + "added_at": "2015-01-15T12:40:03Z", + "primary_color": null, + "video_thumbnail": { + "url": null + }, + "is_local": false, + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + }, + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "href": "https://api.spotify.com/v1/users/jmperezperez" + }, + "track": { + "preview_url": "https://p.scdn.co/mp3-preview/d61fbb7016904624373008ea056d45e6df891071?cid=c7c59b798aab4892ac040a25f7dd1575", + "available_markets": [], + "explicit": false, + "type": "track", + "episode": false, + "track": true, + "album": { + "available_markets": [], + "type": "album", + "album_type": "compilation", + "href": "https://api.spotify.com/v1/albums/6nlfkk5GoXRL1nktlATNsy", + "id": "6nlfkk5GoXRL1nktlATNsy", + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b273aa2ff29970d9a63a49dfaeb2", + "width": 640, + "height": 640 + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e02aa2ff29970d9a63a49dfaeb2", + "width": 300, + "height": 300 + }, + { + "url": "https://i.scdn.co/image/ab67616d00004851aa2ff29970d9a63a49dfaeb2", + "width": 64, + "height": 64 + } + ], + "name": "Wellness & Dreaming Source", + "release_date": "2015-01-09", + "release_date_precision": "day", + "uri": "spotify:album:6nlfkk5GoXRL1nktlATNsy", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/0LyfQWJT6nXafLPZqxe9Of" + }, + "href": "https://api.spotify.com/v1/artists/0LyfQWJT6nXafLPZqxe9Of", + "id": "0LyfQWJT6nXafLPZqxe9Of", + "name": "Various Artists", + "type": "artist", + "uri": "spotify:artist:0LyfQWJT6nXafLPZqxe9Of" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/6nlfkk5GoXRL1nktlATNsy" + }, + "total_tracks": 25 + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/5VQE4WOzPu9h3HnGLuBoA6" + }, + "href": "https://api.spotify.com/v1/artists/5VQE4WOzPu9h3HnGLuBoA6", + "id": "5VQE4WOzPu9h3HnGLuBoA6", + "name": "Vlasta Marek", + "type": "artist", + "uri": "spotify:artist:5VQE4WOzPu9h3HnGLuBoA6" + } + ], + "disc_number": 1, + "track_number": 21, + "duration_ms": 730066, + "external_ids": { + "isrc": "FR2X41475057" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/5o3jMYOSbaVz3tkgwhELSV" + }, + "href": "https://api.spotify.com/v1/tracks/5o3jMYOSbaVz3tkgwhELSV", + "id": "5o3jMYOSbaVz3tkgwhELSV", + "name": "Is", + "popularity": 0, + "uri": "spotify:track:5o3jMYOSbaVz3tkgwhELSV", + "is_local": false + } + }, + { + "added_at": "2015-01-15T12:22:30Z", + "primary_color": null, + "video_thumbnail": { + "url": null + }, + "is_local": false, + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + }, + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "href": "https://api.spotify.com/v1/users/jmperezperez" + }, + "track": { + "preview_url": "https://p.scdn.co/mp3-preview/cc680ec0f5fd5ff21f0cd11ac47e10d3cbb92190?cid=c7c59b798aab4892ac040a25f7dd1575", + "explicit": false, + "type": "track", + "episode": false, + "track": true, + "album": { + "type": "album", + "album_type": "album", + "href": "https://api.spotify.com/v1/albums/4hnqM0JK4CM1phwfq1Ldyz", + "id": "4hnqM0JK4CM1phwfq1Ldyz", + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b273ee0d0dce888c6c8a70db6e8b", + "width": 640, + "height": 640 + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e02ee0d0dce888c6c8a70db6e8b", + "width": 300, + "height": 300 + }, + { + "url": "https://i.scdn.co/image/ab67616d00004851ee0d0dce888c6c8a70db6e8b", + "width": 64, + "height": 64 + } + ], + "name": "This Is Happening", + "release_date": "2010-05-17", + "release_date_precision": "day", + "uri": "spotify:album:4hnqM0JK4CM1phwfq1Ldyz", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/066X20Nz7iquqkkCW6Jxy6" + }, + "href": "https://api.spotify.com/v1/artists/066X20Nz7iquqkkCW6Jxy6", + "id": "066X20Nz7iquqkkCW6Jxy6", + "name": "LCD Soundsystem", + "type": "artist", + "uri": "spotify:artist:066X20Nz7iquqkkCW6Jxy6" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/4hnqM0JK4CM1phwfq1Ldyz" + }, + "total_tracks": 9 + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/066X20Nz7iquqkkCW6Jxy6" + }, + "href": "https://api.spotify.com/v1/artists/066X20Nz7iquqkkCW6Jxy6", + "id": "066X20Nz7iquqkkCW6Jxy6", + "name": "LCD Soundsystem", + "type": "artist", + "uri": "spotify:artist:066X20Nz7iquqkkCW6Jxy6" + } + ], + "disc_number": 1, + "track_number": 4, + "duration_ms": 401440, + "external_ids": { + "isrc": "US4GE1000022" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/4Cy0NHJ8Gh0xMdwyM9RkQm" + }, + "href": "https://api.spotify.com/v1/tracks/4Cy0NHJ8Gh0xMdwyM9RkQm", + "id": "4Cy0NHJ8Gh0xMdwyM9RkQm", + "name": "All I Want", + "popularity": 45, + "uri": "spotify:track:4Cy0NHJ8Gh0xMdwyM9RkQm", + "is_local": false + } + }, + { + "added_at": "2015-01-15T12:40:35Z", + "primary_color": null, + "video_thumbnail": { + "url": null + }, + "is_local": false, + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + }, + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "href": "https://api.spotify.com/v1/users/jmperezperez" + }, + "track": { + "preview_url": "https://p.scdn.co/mp3-preview/d6ecf1f98d0b1fdc8c535de8e2010d0d8b8d040b?cid=c7c59b798aab4892ac040a25f7dd1575", + "explicit": false, + "type": "track", + "episode": false, + "track": true, + "album": { + "type": "album", + "album_type": "album", + "href": "https://api.spotify.com/v1/albums/2usKFntxa98WHMcyW6xJBz", + "id": "2usKFntxa98WHMcyW6xJBz", + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b2738b7447ac3daa1da18811cf7b", + "width": 640, + "height": 640 + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e028b7447ac3daa1da18811cf7b", + "width": 300, + "height": 300 + }, + { + "url": "https://i.scdn.co/image/ab67616d000048518b7447ac3daa1da18811cf7b", + "width": 64, + "height": 64 + } + ], + "name": "Glenn Horiuchi Trio / Gelenn Horiuchi Quartet: Mercy / Jump Start / Endpoints / Curl Out / Earthworks / Mind Probe / Null Set / Another Space (A)", + "release_date": "2011-04-01", + "release_date_precision": "day", + "uri": "spotify:album:2usKFntxa98WHMcyW6xJBz", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/272ArH9SUAlslQqsSgPJA2" + }, + "href": "https://api.spotify.com/v1/artists/272ArH9SUAlslQqsSgPJA2", + "id": "272ArH9SUAlslQqsSgPJA2", + "name": "Glenn Horiuchi Trio", + "type": "artist", + "uri": "spotify:artist:272ArH9SUAlslQqsSgPJA2" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/2usKFntxa98WHMcyW6xJBz" + }, + "total_tracks": 8 + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/272ArH9SUAlslQqsSgPJA2" + }, + "href": "https://api.spotify.com/v1/artists/272ArH9SUAlslQqsSgPJA2", + "id": "272ArH9SUAlslQqsSgPJA2", + "name": "Glenn Horiuchi Trio", + "type": "artist", + "uri": "spotify:artist:272ArH9SUAlslQqsSgPJA2" + } + ], + "disc_number": 1, + "track_number": 2, + "duration_ms": 358760, + "external_ids": { + "isrc": "USB8U1025969" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/6hvFrZNocdt2FcKGCSY5NI" + }, + "href": "https://api.spotify.com/v1/tracks/6hvFrZNocdt2FcKGCSY5NI", + "id": "6hvFrZNocdt2FcKGCSY5NI", + "name": "Endpoints", + "popularity": 0, + "uri": "spotify:track:6hvFrZNocdt2FcKGCSY5NI", + "is_local": false + } + }, + { + "added_at": "2015-01-15T12:41:10Z", + "primary_color": null, + "video_thumbnail": { + "url": null + }, + "is_local": false, + "added_by": { + "external_urls": { + "spotify": "https://open.spotify.com/user/jmperezperez" + }, + "id": "jmperezperez", + "type": "user", + "uri": "spotify:user:jmperezperez", + "href": "https://api.spotify.com/v1/users/jmperezperez" + }, + "track": { + "preview_url": "https://p.scdn.co/mp3-preview/47b974e463b1e862c7b3c18fa2ceedc513f2106b?cid=c7c59b798aab4892ac040a25f7dd1575", + "available_markets": [], + "explicit": false, + "type": "track", + "episode": false, + "track": true, + "album": { + "available_markets": [], + "type": "album", + "album_type": "album", + "href": "https://api.spotify.com/v1/albums/0ivM6kSawaug0j3tZVusG2", + "id": "0ivM6kSawaug0j3tZVusG2", + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b27304e57d181ff062f8339d6c71", + "width": 640, + "height": 640 + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e0204e57d181ff062f8339d6c71", + "width": 300, + "height": 300 + }, + { + "url": "https://i.scdn.co/image/ab67616d0000485104e57d181ff062f8339d6c71", + "width": 64, + "height": 64 + } + ], + "name": "All The Best (Spanish Version)", + "release_date": "2007-01-01", + "release_date_precision": "day", + "uri": "spotify:album:0ivM6kSawaug0j3tZVusG2", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2KftmGt9sk1yLjsAoloC3M" + }, + "href": "https://api.spotify.com/v1/artists/2KftmGt9sk1yLjsAoloC3M", + "id": "2KftmGt9sk1yLjsAoloC3M", + "name": "Zucchero", + "type": "artist", + "uri": "spotify:artist:2KftmGt9sk1yLjsAoloC3M" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/0ivM6kSawaug0j3tZVusG2" + }, + "total_tracks": 18 + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/2KftmGt9sk1yLjsAoloC3M" + }, + "href": "https://api.spotify.com/v1/artists/2KftmGt9sk1yLjsAoloC3M", + "id": "2KftmGt9sk1yLjsAoloC3M", + "name": "Zucchero", + "type": "artist", + "uri": "spotify:artist:2KftmGt9sk1yLjsAoloC3M" + } + ], + "disc_number": 1, + "track_number": 18, + "duration_ms": 176093, + "external_ids": { + "isrc": "ITUM70701043" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/2E2znCPaS8anQe21GLxcvJ" + }, + "href": "https://api.spotify.com/v1/tracks/2E2znCPaS8anQe21GLxcvJ", + "id": "2E2znCPaS8anQe21GLxcvJ", + "name": "You Are So Beautiful", + "popularity": 0, + "uri": "spotify:track:2E2znCPaS8anQe21GLxcvJ", + "is_local": false + } + } + ] + } +} diff --git a/tests/components/spotify/test_media_player.py b/tests/components/spotify/test_media_player.py new file mode 100644 index 00000000000..07e20eca35f --- /dev/null +++ b/tests/components/spotify/test_media_player.py @@ -0,0 +1,172 @@ +"""Tests for the Spotify media player platform.""" + +from unittest.mock import MagicMock + +import pytest +from spotipy import SpotifyException + +from homeassistant.components.media_player import ( + MediaPlayerEntityFeature, + MediaPlayerState, +) +from homeassistant.components.spotify import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry, load_json_value_fixture + + +@pytest.mark.usefixtures("setup_credentials") +async def test_entities( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test the Spotify entities.""" + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert state.state == MediaPlayerState.PLAYING + assert state.attributes["media_content_type"] == "music" + assert state.attributes["media_duration"] == 296.466 + assert state.attributes["media_position"] == 249.367 + assert "media_position_updated_at" in state.attributes + assert state.attributes["media_title"] == "The Spirit Of Radio" + assert state.attributes["media_artist"] == "Rush" + assert state.attributes["media_album_name"] == "Permanent Waves" + assert state.attributes["media_track"] == 1 + assert state.attributes["repeat"] == "off" + assert state.attributes["shuffle"] is False + assert state.attributes["volume_level"] == 0.25 + assert state.attributes["source"] == "Master Bathroom Speaker" + assert state.attributes["supported_features"] == ( + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.VOLUME_SET + ) + + +@pytest.mark.usefixtures("setup_credentials") +async def test_podcast( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test the Spotify entities while listening a podcast.""" + mock_spotify.return_value.current_playback.return_value = load_json_value_fixture( + "playback_episode.json", DOMAIN + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert state.state == MediaPlayerState.PLAYING + assert state.attributes["media_content_type"] == "podcast" + assert state.attributes["media_duration"] == 3690.161 + assert state.attributes["media_position"] == 5.41 + assert "media_position_updated_at" in state.attributes + assert ( + state.attributes["media_title"] + == "My Squirrel Has Brain Damage - Safety Third 119" + ) + assert state.attributes["media_artist"] == "Safety Third " + assert state.attributes["media_album_name"] == "Safety Third" + assert state.attributes["repeat"] == "off" + assert state.attributes["shuffle"] is False + assert state.attributes["volume_level"] == 0.46 + assert state.attributes["source"] == "Sonos Roam SL" + assert ( + state.attributes["supported_features"] == MediaPlayerEntityFeature.SELECT_SOURCE + ) + + +@pytest.mark.usefixtures("setup_credentials") +async def test_free_account( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Spotify entities with a free account.""" + mock_spotify.return_value.me.return_value["product"] = "free" + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert state.attributes["supported_features"] == 0 + + +@pytest.mark.usefixtures("setup_credentials") +async def test_restricted_device( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Spotify entities with a restricted device.""" + mock_spotify.return_value.current_playback.return_value["device"][ + "is_restricted" + ] = True + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert ( + state.attributes["supported_features"] == MediaPlayerEntityFeature.SELECT_SOURCE + ) + + +@pytest.mark.usefixtures("setup_credentials") +async def test_spotify_dj_list( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Spotify entities with a Spotify DJ playlist.""" + mock_spotify.return_value.current_playback.return_value["context"]["uri"] = ( + "spotify:playlist:37i9dQZF1EYkqdzj48dyYq" + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert state.attributes["media_playlist"] == "DJ" + + +@pytest.mark.usefixtures("setup_credentials") +async def test_fetching_playlist_does_not_fail( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test failing fetching playlist does not fail update.""" + mock_spotify.return_value.playlist.side_effect = SpotifyException( + 404, "Not Found", "msg" + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert "media_playlist" not in state.attributes + + +@pytest.mark.usefixtures("setup_credentials") +async def test_idle( + hass: HomeAssistant, + mock_spotify: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the Spotify entities in idle state.""" + mock_spotify.return_value.current_playback.return_value = {} + await setup_integration(hass, mock_config_entry) + state = hass.states.get("media_player.spotify_spotify_1") + assert state + assert state.state == MediaPlayerState.IDLE + assert ( + state.attributes["supported_features"] == MediaPlayerEntityFeature.SELECT_SOURCE + )