From f394e36d82a85aff881812163ade0ec87888c86b Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Sun, 7 Apr 2019 19:07:31 +0000 Subject: Remove 'no cover' pragmas --- pandora/client.py | 48 +++++++------- pandora/models/_base.py | 2 +- pandora/models/ad.py | 10 +-- pandora/models/playlist.py | 20 +++--- pandora/models/search.py | 2 +- pandora/models/station.py | 2 +- setup.cfg | 6 -- tests/test_pandora/test_client.py | 134 ++++++++++++++++++++++++++++++++++++++ tests/test_pandora/test_models.py | 93 +++++++++++++++++++++++--- 9 files changed, 261 insertions(+), 56 deletions(-) diff --git a/pandora/client.py b/pandora/client.py index bb30738..e0d3da9 100644 --- a/pandora/client.py +++ b/pandora/client.py @@ -102,14 +102,14 @@ class APIClient(BaseAPIClient): This is what clients should actually use. """ - def get_station_list(self): # pragma: no cover + def get_station_list(self): from .models.station import StationList return StationList.from_json(self, self("user.getStationList", includeStationArtUrl=True)) - def get_station_list_checksum(self): # pragma: no cover + def get_station_list_checksum(self): return self("user.getStationListChecksum")["checksum"] def get_playlist(self, station_token, additional_urls=None): @@ -142,13 +142,13 @@ class APIClient(BaseAPIClient): return playlist - def get_bookmarks(self): # pragma: no cover + def get_bookmarks(self): from .models.bookmark import BookmarkList return BookmarkList.from_json(self, self("user.getBookmarks")) - def get_station(self, station_token): # pragma: no cover + def get_station(self, station_token): from .models.station import Station return Station.from_json(self, @@ -156,25 +156,25 @@ class APIClient(BaseAPIClient): stationToken=station_token, includeExtendedAttributes=True)) - def add_artist_bookmark(self, track_token): # pragma: no cover + def add_artist_bookmark(self, track_token): return self("bookmark.addArtistBookmark", trackToken=track_token) - def add_song_bookmark(self, track_token): # pragma: no cover + def add_song_bookmark(self, track_token): return self("bookmark.addSongBookmark", trackToken=track_token) - def delete_song_bookmark(self, bookmark_token): # pragma: no cover + def delete_song_bookmark(self, bookmark_token): return self("bookmark.deleteSongBookmark", bookmarkToken=bookmark_token) - def delete_artist_bookmark(self, bookmark_token): # pragma: no cover + def delete_artist_bookmark(self, bookmark_token): return self("bookmark.deleteArtistBookmark", bookmarkToken=bookmark_token) def search(self, search_text, include_near_matches=False, - include_genre_stations=False): # pragma: no cover + include_genre_stations=False): from .models.search import SearchResult return SearchResult.from_json( @@ -185,12 +185,12 @@ class APIClient(BaseAPIClient): includeGenreStations=include_genre_stations) ) - def add_feedback(self, track_token, positive): # pragma: no cover + def add_feedback(self, track_token, positive): return self("station.addFeedback", trackToken=track_token, isPositive=positive) - def add_music(self, music_token, station_token): # pragma: no cover + def add_music(self, music_token, station_token): return self("station.addMusic", musicToken=music_token, stationToken=station_token) @@ -213,15 +213,15 @@ class APIClient(BaseAPIClient): return Station.from_json(self, self("station.createStation", **kwargs)) - def delete_feedback(self, feedback_id): # pragma: no cover + def delete_feedback(self, feedback_id): return self("station.deleteFeedback", feedbackId=feedback_id) - def delete_music(self, seed_id): # pragma: no cover + def delete_music(self, seed_id): return self("station.deleteMusic", seedId=seed_id) - def delete_station(self, station_token): # pragma: no cover + def delete_station(self, station_token): return self("station.deleteStation", stationToken=station_token) @@ -234,37 +234,37 @@ class APIClient(BaseAPIClient): return genre_stations - def get_genre_stations_checksum(self): # pragma: no cover + def get_genre_stations_checksum(self): return self("station.getGenreStationsChecksum")["checksum"] - def rename_station(self, station_token, name): # pragma: no cover + def rename_station(self, station_token, name): return self("station.renameStation", stationToken=station_token, stationName=name) - def explain_track(self, track_token): # pragma: no cover + def explain_track(self, track_token): return self("track.explainTrack", trackToken=track_token) - def set_quick_mix(self, *args): # pragma: no cover + def set_quick_mix(self, *args): return self("user.setQuickMix", quickMixStationIds=args) - def sleep_song(self, track_token): # pragma: no cover + def sleep_song(self, track_token): return self("user.sleepSong", trackToken=track_token) - def share_station(self, station_id, station_token, *emails): # pragma: nc + def share_station(self, station_id, station_token, *emails): return self("station.shareStation", stationId=station_id, stationToken=station_token, emails=emails) - def transform_shared_station(self, station_token): # pragma: no cover + def transform_shared_station(self, station_token): return self("station.transformSharedStation", stationToken=station_token) - def share_music(self, music_token, *emails): # pragma: no cover + def share_music(self, music_token, *emails): return self("music.shareMusic", musicToken=music_token, email=emails[0]) @@ -282,13 +282,13 @@ class APIClient(BaseAPIClient): ad_item.ad_token = ad_token return ad_item - def get_ad_metadata(self, ad_token): # pragma: no cover + def get_ad_metadata(self, ad_token): return self("ad.getAdMetadata", adToken=ad_token, returnAdTrackingTokens=True, supportAudioAds=True) - def register_ad(self, station_id, tokens): # pragma: no cover + def register_ad(self, station_id, tokens): return self("ad.registerAd", stationId=station_id, adTrackingTokens=tokens) diff --git a/pandora/models/_base.py b/pandora/models/_base.py index 5689cdf..6975d0a 100644 --- a/pandora/models/_base.py +++ b/pandora/models/_base.py @@ -38,7 +38,7 @@ class SyntheticField(namedtuple("SyntheticField", ["field"])): payload. """ - def formatter(self, api_client, data, newval): # pragma: no cover + def formatter(self, api_client, data, newval): """Format Value for Model The return value of this method is used as a value for the field in the diff --git a/pandora/models/ad.py b/pandora/models/ad.py index 040c76b..ad4b7b0 100644 --- a/pandora/models/ad.py +++ b/pandora/models/ad.py @@ -35,17 +35,17 @@ class AdItem(PlaylistModel): raise exc return super().prepare_playback() - def thumbs_up(self): # pragma: no cover + def thumbs_up(self): return - def thumbs_down(self): # pragma: no cover + def thumbs_down(self): return - def bookmark_song(self): # pragma: no cover + def bookmark_song(self): return - def bookmark_artist(self): # pragma: no cover + def bookmark_artist(self): return - def sleep(self): # pragma: no cover + def sleep(self): return diff --git a/pandora/models/playlist.py b/pandora/models/playlist.py index bd135db..38afb00 100644 --- a/pandora/models/playlist.py +++ b/pandora/models/playlist.py @@ -119,19 +119,19 @@ class PlaylistModel(PandoraModel): """ return self - def thumbs_up(self): # pragma: no cover + def thumbs_up(self): raise NotImplementedError - def thumbs_down(self): # pragma: no cover + def thumbs_down(self): raise NotImplementedError - def bookmark_song(self): # pragma: no cover + def bookmark_song(self): raise NotImplementedError - def bookmark_artist(self): # pragma: no cover + def bookmark_artist(self): raise NotImplementedError - def sleep(self): # pragma: no cover + def sleep(self): raise NotImplementedError @@ -175,19 +175,19 @@ class PlaylistItem(PlaylistModel): def is_ad(self): return self.ad_token is not None - def thumbs_up(self): # pragma: no cover + def thumbs_up(self): return self._api_client.add_feedback(self.track_token, True) - def thumbs_down(self): # pragma: no cover + def thumbs_down(self): return self._api_client.add_feedback(self.track_token, False) - def bookmark_song(self): # pragma: no cover + def bookmark_song(self): return self._api_client.add_song_bookmark(self.track_token) - def bookmark_artist(self): # pragma: no cover + def bookmark_artist(self): return self._api_client.add_artist_bookmark(self.track_token) - def sleep(self): # pragma: no cover + def sleep(self): return self._api_client.sleep_song(self.track_token) diff --git a/pandora/models/search.py b/pandora/models/search.py index 89def8e..94e6ee6 100644 --- a/pandora/models/search.py +++ b/pandora/models/search.py @@ -24,7 +24,7 @@ class SearchResultItem(PandoraModel): def is_genre_station(self): return isinstance(self, GenreStationSearchResultItem) - def create_station(self): # pragma: no cover + def create_station(self): raise NotImplementedError @classmethod diff --git a/pandora/models/station.py b/pandora/models/station.py index d4f4846..a1880ec 100644 --- a/pandora/models/station.py +++ b/pandora/models/station.py @@ -104,7 +104,7 @@ class GenreStation(PandoraModel): token = Field("stationToken") category = Field("categoryName") - def get_playlist(self): # pragma: no cover + def get_playlist(self): raise NotImplementedError("Genre stations do not have playlists. " "Create a real station using the token.") diff --git a/setup.cfg b/setup.cfg index 2c43f4f..1c8c41f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,9 +4,3 @@ validate = release [coverage:run] branch = True - -[coverage:report] -# Support "no cover" and "nc" to handle long lines -exclude_lines = - pragma: no cover - pragma: nc diff --git a/tests/test_pandora/test_client.py b/tests/test_pandora/test_client.py index 9945bff..69eac65 100644 --- a/tests/test_pandora/test_client.py +++ b/tests/test_pandora/test_client.py @@ -3,6 +3,10 @@ from unittest.mock import Mock, call, patch from pandora import errors from pandora.models.ad import AdItem +from pandora.models.station import Station +from pandora.models.station import StationList +from pandora.models.search import SearchResult +from pandora.models.bookmark import BookmarkList from pandora.models.playlist import AdditionalAudioUrl from pandora.client import APIClient, BaseAPIClient from tests.test_pandora.test_models import TestAdItem @@ -253,3 +257,133 @@ class TestAdditionalUrls(TestCase): includeTrackLength=True, stationToken='token_mock', xplatformAdCapable=True)]) + + +# On the surface this test class seems dumb because it's mostly just exercising +# pass-throughs to the transport but it exists to ensure no subtle errors get +# introduced to API client methods that will only be spotted at runtime (import +# errors, etc...) +class TestAPIClientExhaustive(TestCase): + + def setUp(self): + self.transport = Mock() + self.api = APIClient(self.transport, "puser", "ppass", "device") + + def test_register_ad(self): + self.api.register_ad("sid", "tokens") + self.transport.assert_called_with( + "ad.registerAd", stationId="sid", adTrackingTokens="tokens") + + def test_share_music(self): + self.api.share_music("token", "foo@example.com") + self.transport.assert_called_with( + "music.shareMusic", musicToken="token", email="foo@example.com") + + def test_transform_shared_station(self): + self.api.transform_shared_station("token") + self.transport.assert_called_with( + "station.transformSharedStation", stationToken="token") + + def test_share_station(self): + self.api.share_station("sid", "token", "foo@example.com") + self.transport.assert_called_with( + "station.shareStation", stationId="sid", stationToken="token", + emails=("foo@example.com",)) + + def test_sleep_song(self): + self.api.sleep_song("token") + self.transport.assert_called_with("user.sleepSong", trackToken="token") + + def test_set_quick_mix(self): + self.api.set_quick_mix("id") + self.transport.assert_called_with( + "user.setQuickMix", quickMixStationIds=("id",)) + + def test_explain_track(self): + self.api.explain_track("token") + self.transport.assert_called_with( + "track.explainTrack", trackToken="token") + + def test_rename_station(self): + self.api.rename_station("token", "name") + self.transport.assert_called_with( + "station.renameStation", stationToken="token", stationName="name") + + def test_delete_station(self): + self.api.delete_station("token") + self.transport.assert_called_with( + "station.deleteStation", stationToken="token") + + def test_delete_music(self): + self.api.delete_music("seed") + self.transport.assert_called_with("station.deleteMusic", seedId="seed") + + def test_delete_feedback(self): + self.api.delete_feedback("id") + self.transport.assert_called_with( + "station.deleteFeedback", feedbackId="id") + + def test_add_music(self): + self.api.add_music("mt", "st") + self.transport.assert_called_with( + "station.addMusic", musicToken="mt", stationToken="st") + + def test_add_feedback(self): + self.api.add_feedback("token", False) + self.transport.assert_called_with( + "station.addFeedback", trackToken="token", isPositive=False) + + def test_add_artist_bookmark(self): + self.api.add_artist_bookmark("tt") + self.transport.assert_called_with( + "bookmark.addArtistBookmark", trackToken="tt") + + def test_add_song_bookmark(self): + self.api.add_song_bookmark("tt") + self.transport.assert_called_with( + "bookmark.addSongBookmark", trackToken="tt") + + def test_delete_song_bookmark(self): + self.api.delete_song_bookmark("bt") + self.transport.assert_called_with( + "bookmark.deleteSongBookmark", bookmarkToken="bt") + + def test_delete_artist_bookmark(self): + self.api.delete_artist_bookmark("bt") + self.transport.assert_called_with( + "bookmark.deleteArtistBookmark", bookmarkToken="bt") + + def test_get_station_list_checksum(self): + self.transport.return_value = {"checksum": "foo"} + self.assertEqual("foo", self.api.get_station_list_checksum()) + self.transport.assert_called_with("user.getStationListChecksum") + + # The following methods use the bare minimum JSON required to construct the + # models for more detailed model tests look at test_models instead + + def test_get_station_list(self): + self.transport.return_value = {"stations": []} + self.assertIsInstance(self.api.get_station_list(), StationList) + self.transport.assert_called_with( + "user.getStationList", includeStationArtUrl=True) + + def test_get_bookmarks(self): + self.transport.return_value = {} + self.assertIsInstance(self.api.get_bookmarks(), BookmarkList) + self.transport.assert_called_with("user.getBookmarks") + + def test_get_station(self): + self.transport.return_value = {} + self.assertIsInstance(self.api.get_station("st"), Station) + self.transport.assert_called_with( + "station.getStation", stationToken="st", + includeExtendedAttributes=True) + + def test_search(self): + self.transport.return_value = {} + self.assertIsInstance(self.api.search( + "text", include_near_matches=True, include_genre_stations=True), + SearchResult) + self.transport.assert_called_with( + "music.search", searchText="text", includeNearMatches=True, + includeGenreStations=True) diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py index 0d37ca0..cb650df 100644 --- a/tests/test_pandora/test_models.py +++ b/tests/test_pandora/test_models.py @@ -277,6 +277,11 @@ class TestPlaylistItemModel(TestCase): AUDIO_URL_NO_MAP = {"audioUrl": "foo"} WEIRD_FORMAT = {"audioUrlMap": {"highQuality": {}}} + def setUp(self): + self.client = Mock() + self.playlist = plm.PlaylistItem(self.client) + self.playlist.track_token = "token" + def test_audio_url_without_map(self): item = plm.PlaylistItem.from_json(Mock(), self.AUDIO_URL_NO_MAP) self.assertEqual(item.bitrate, 64) @@ -293,20 +298,57 @@ class TestPlaylistItemModel(TestCase): self.assertIsNone(item.encoding) self.assertIsNone(item.audio_url) + def test_thumbs_up(self): + self.playlist.thumbs_up() + self.client.add_feedback.assert_called_with("token", True) + + def test_thumbs_down(self): + self.playlist.thumbs_down() + self.client.add_feedback.assert_called_with("token", False) + + def test_bookmark_song(self): + self.playlist.bookmark_song() + self.client.add_song_bookmark.assert_called_with("token") + + def test_bookmark_artist(self): + self.playlist.bookmark_artist() + self.client.add_artist_bookmark.assert_called_with("token") + + def test_sleep_song(self): + self.playlist.sleep() + self.client.sleep_song.assert_called_with("token") + class TestPlaylistModel(TestCase): + def setUp(self): + self.client = Mock() + self.playlist = plm.PlaylistModel(self.client) + def test_unplayable_get_is_playable(self): - playlist = plm.PlaylistModel(Mock()) - playlist.audio_url = "" - self.assertFalse(playlist.get_is_playable()) + self.playlist.audio_url = "" + self.assertFalse(self.playlist.get_is_playable()) def test_playable_get_is_playable(self): - client = Mock() - playlist = plm.PlaylistModel(client) - playlist.audio_url = "foo" - playlist.get_is_playable() - client.transport.test_url.assert_called_with("foo") + self.playlist.audio_url = "foo" + self.playlist.get_is_playable() + self.client.transport.test_url.assert_called_with("foo") + + def test_not_implemented_interface_methods(self): + with self.assertRaises(NotImplementedError): + self.playlist.thumbs_up() + + with self.assertRaises(NotImplementedError): + self.playlist.thumbs_down() + + with self.assertRaises(NotImplementedError): + self.playlist.bookmark_song() + + with self.assertRaises(NotImplementedError): + self.playlist.bookmark_artist() + + with self.assertRaises(NotImplementedError): + self.playlist.sleep() class TestAdItem(TestCase): @@ -393,6 +435,13 @@ class TestAdItem(TestCase): assert self.result.register_ad.called assert super_mock.called + def test_noop_methods(self): + self.assertIsNone(self.result.thumbs_up()) + self.assertIsNone(self.result.thumbs_down()) + self.assertIsNone(self.result.bookmark_song()) + self.assertIsNone(self.result.bookmark_artist()) + self.assertIsNone(self.result.sleep()) + class TestSearchResultItem(TestCase): @@ -471,6 +520,12 @@ class TestSearchResultItem(TestCase): sm.SearchResultItem.from_json( self.api_client_mock, self.UNKNOWN_JSON_DATA) + def test_interface(self): + result = sm.SearchResultItem(self.api_client_mock) + + with self.assertRaises(NotImplementedError): + result.create_station() + class TestArtistSearchResultItem(TestCase): @@ -642,6 +697,19 @@ class TestGenreStationList(TestCase): self.assertTrue(stations.has_changed()) +class TestGenreStation(TestCase): + + TEST_DATA = {"categoryName": "foo", "stations": []} + + def test_get_playlist_throws_exception(self): + api_client = Mock() + genre_station = stm.GenreStation.from_json(api_client, self.TEST_DATA) + + with self.assertRaisesRegex( + NotImplementedError, "Genre stations do not have playlists.*"): + genre_station.get_playlist() + + class TestStationList(TestCase): TEST_DATA = { @@ -694,3 +762,12 @@ class TestPandoraType(TestCase): def test_it_returns_genre_for_unknown_string(self): pt = plm.PandoraType.from_string("FOO") self.assertIs(plm.PandoraType.GENRE, pt) + + +class TestSyntheticField(TestCase): + + def test_interface(self): + sf = m.SyntheticField(field="foo") + + with self.assertRaises(NotImplementedError): + sf.formatter(None, None, None) -- cgit v1.2.3