"""Spotify API wrapper tests.""" import datetime from gzip import GzipFile from io import BytesIO import itertools from tempfile import TemporaryFile from unittest.mock import ANY, call, MagicMock from unittest.mock import patch import pytest from feed_ingestion.flows.spotify.spotify_api import SpotifyAPI @pytest.fixture def spotify_api_authenticated(): """Yield SpotifyAPI Wrapper fixture (authenticated).""" spotify_api = SpotifyAPI('test', 'secret', 'test-licensor', 'test_version') spotify_api._token = 'token' yield spotify_api @pytest.fixture def spotify_api(): """Spotify API Wrapper fixture.""" yield SpotifyAPI('test', 'secret', 'test-licensor', 'test_version') @pytest.fixture def mock_oauth_success(): """Yield Spotify API oauth success response.""" requests_path = ( 'feed_ingestion.flows.spotify.spotify_api.requests') with patch(requests_path) as requests: response_body = {'access_token': 'token', 'expires_in': 28800} response_mock = MagicMock() response_mock.status_code = 200 response_mock.json = MagicMock(return_value=response_body) requests.post = MagicMock(return_value=response_mock) yield requests @pytest.fixture def mock_oauth_failure(): """Yield Spotify API oauth failure response.""" requests_path = ( 'feed_ingestion.flows.spotify.spotify_api.requests') with patch(requests_path) as requests: response_mock = MagicMock() response_mock.status_code = 401 requests.post = MagicMock(return_value=response_mock) yield requests @pytest.fixture def mock_tracks_content(): """Mock Tracks Resource response.""" content = ( b'{"message": "APITrackData", "version": 2, "track_id": "0",' b' "uri": "spotify:track:0", "isrc": "0", "album_code": "0",' b' "album_artist": "Test", "track_artists": "Test",' b'"track_name": "Test", "album_name": "Test"}\n' b'{"message": "APITrackData", "version": 2, "track_id": "1",' b' "uri": "spotify:track:1", "isrc": "1", "album_code": "1",' b' "album_artist": "Test1", "track_artists": "Test1",' b'"track_name": "Test1", "album_name": "Test1"}') out = BytesIO() with GzipFile(fileobj=out, mode='wb') as f: f.write(content) yield out.getvalue() @pytest.fixture def expected_tracks_content(): """Json response for Tracks resource.""" return [ {'message': 'APITrackData', 'version': 2, 'track_id': '0', 'uri': 'spotify:track:0', 'isrc': '0', 'album_code': '0', 'album_artist': 'Test', 'track_artists': 'Test', 'track_name': 'Test', 'album_name': 'Test'}, {'message': 'APITrackData', 'version': 2, 'track_id': '1', 'uri': 'spotify:track:1', 'isrc': '1', 'album_code': '1', 'album_artist': 'Test1', 'track_artists': 'Test1', 'track_name': 'Test1', 'album_name': 'Test1'}] @pytest.fixture def mock_session(mock_tracks_content): """Mock requests session.""" session_path = ( 'feed_ingestion.flows.spotify.spotify_api.Session') def iter_content_mock(chunk_size=128): it = iter(mock_tracks_content) while True: chunk = bytes(itertools.islice(it, chunk_size)) if not chunk: return yield chunk with patch(session_path) as mock_session: session_instance = mock_session.return_value response_mock = MagicMock() response_mock.iter_content = iter_content_mock response_mock.content = mock_tracks_content session_instance.send = MagicMock(return_value=response_mock) yield session_instance def assert_get_to_file( fd, mock_content, mock_session, date, resource_name, licensor_name, country=None): """Test get_*_to_file methods.""" assert_request(country, date, licensor_name, mock_session, resource_name) fd.seek(0) for line in fd: assert line in mock_content def assert_get_to_json( res, mock_content, mock_session, date, resource_name, licensor_name, country=None): """Test get_*_to_json methods.""" assert_request(country, date, licensor_name, mock_session, resource_name) assert res == mock_content def assert_request(country, date, licensor_name, mock_session, resource_name): """Test API request.""" assert mock_session.send.called req = mock_session.send.call_args[0][0] assert licensor_name in req.url assert resource_name in req.url assert date in req.url if country is not None: assert country in req.url def test_version_api_v1(): """Test Spotify API choosing version.""" spotify_api = SpotifyAPI('test', 'secret', 'test-licensor', 'v1') assert spotify_api.version == 'v1' def test_version_api_v2(): """Test Spotify API choosing version.""" spotify_api = SpotifyAPI('test', 'secret', 'test-licensor', 'v2') assert spotify_api.version == 'v2' def test_version_api_default(): """Test Spotify API choosing version.""" spotify_api = SpotifyAPI('test', 'secret', 'test-licensor') assert spotify_api.version == 'v1' def test_token_success(mock_oauth_success, spotify_api): """Test Spotify API token request success scenario.""" spotify_api._token = None token = spotify_api.token assert token == 'token' assert spotify_api._token == 'token' def test_token_failure(mock_oauth_failure, spotify_api): """Test Spotify API token request failure scenario.""" spotify_api._token = None with pytest.raises(Exception): spotify_api.token def test_token_cache(mock_oauth_success, spotify_api): """Should not call Spotify API if token is already cached.""" spotify_api._token = 'cached-token' token = spotify_api.token mock_oauth_success.post.assert_not_called() assert token == 'cached-token' def test_tracks_to_file( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_tracks_to_file.""" with TemporaryFile() as fd: spotify_api_authenticated.get_tracks_to_file(fd, '2017-10-01') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'tracks', spotify_api_authenticated.licensor_name) def test_tracks_to_file_date( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_tracks_to_file with date argument.""" with TemporaryFile() as fd: d = datetime.date(2017, 10, 1) spotify_api_authenticated.get_tracks_to_file(fd, d) assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'tracks', spotify_api_authenticated.licensor_name) def test_tracks_to_json( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_tracks_to_json.""" res = spotify_api_authenticated.get_tracks_to_json('2017-10-01') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'tracks', spotify_api_authenticated.licensor_name) def test_tracks_to_json_date( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_tracks_to_json with date argument.""" d = datetime.date(2017, 10, 1) res = spotify_api_authenticated.get_tracks_to_json(d) assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'tracks', spotify_api_authenticated.licensor_name) def test_users_to_file( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_users_to_file.""" with TemporaryFile() as fd: spotify_api_authenticated.get_users_to_file(fd, '2017-10-01') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'users', spotify_api_authenticated.licensor_name) def test_users_to_file_date( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_users_to_file with date argument.""" with TemporaryFile() as fd: d = datetime.date(2017, 10, 1) spotify_api_authenticated.get_users_to_file(fd, d) assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'users', spotify_api_authenticated.licensor_name) def test_users_to_json( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_users_to_json.""" res = spotify_api_authenticated.get_users_to_json('2017-10-01') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'users', spotify_api_authenticated.licensor_name) def test_users_to_json_date( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_users_to_json with date argument.""" d = datetime.date(2017, 10, 1) res = spotify_api_authenticated.get_users_to_json(d) assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'users', spotify_api_authenticated.licensor_name) def test_streams_to_file( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_streams_to_file.""" with TemporaryFile() as fd: spotify_api_authenticated.get_streams_to_file(fd, '2017-10-01', 'AT') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'streams', spotify_api_authenticated.licensor_name, 'AT') def test_streams_to_file_date( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_streams_to_file with date argument.""" with TemporaryFile() as fd: d = datetime.date(2017, 10, 1) spotify_api_authenticated.get_streams_to_file(fd, d, 'AT') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'streams', spotify_api_authenticated.licensor_name, 'AT') def test_streams_to_json( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_streams_to_json.""" res = spotify_api_authenticated.get_streams_to_json('2017-10-01', 'AT') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'streams', spotify_api_authenticated.licensor_name) def test_streams_to_json_date( mock_session, spotify_api_authenticated, mock_tracks_content, expected_tracks_content): """Test get_streams_to_json with date argument.""" d = datetime.date(2017, 10, 1) res = spotify_api_authenticated.get_streams_to_json(d, 'AT') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'streams', spotify_api_authenticated.licensor_name) def test_aggregated_streams_to_file( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_aggregated_streams_to_file.""" with TemporaryFile() as fd: spotify_api_authenticated \ .get_aggregated_streams_to_file(fd, '2017-10-01') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'aggregatedstreams', spotify_api_authenticated.licensor_name) def test_aggregated_streams_to_file_date( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_aggregated_streams_to_file with date argument.""" with TemporaryFile() as fd: d = datetime.date(2017, 10, 1) spotify_api_authenticated.get_aggregated_streams_to_file(fd, d) assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'aggregatedstreams', spotify_api_authenticated.licensor_name) def test_aggregated_streams_to_json( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_aggregated_streams_to_json.""" res = spotify_api_authenticated \ .get_aggregated_streams_to_json('2017-10-01') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'aggregatedstreams', spotify_api_authenticated.licensor_name) def test_aggregated_streams_to_json_date( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_aggregated_streams_to_json with date argument.""" d = datetime.date(2017, 10, 1) res = spotify_api_authenticated.get_aggregated_streams_to_json(d) assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'aggregatedstreams', spotify_api_authenticated.licensor_name) def test_sub_30_sec_streams_to_file( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_aggregated_streams_to_file.""" with TemporaryFile() as fd: spotify_api_authenticated.get_sub_30_sec_streams_to_file( fd, '2017-10-01', 'AT') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'sub_30_sec_streams', spotify_api_authenticated.licensor_name, 'AT') def test_sub_30_sec_streams_to_file_date( mock_session, spotify_api_authenticated, mock_tracks_content): """Test get_aggregated_streams_to_file with date argument.""" with TemporaryFile() as fd: d = datetime.date(2017, 10, 1) spotify_api_authenticated.get_sub_30_sec_streams_to_file(fd, d, 'AT') assert_get_to_file( fd, mock_tracks_content, mock_session, '2017/10/01', 'sub_30_sec_streams', spotify_api_authenticated.licensor_name, 'AT') def test_sub_30_sec_streams_to_json( mock_session, spotify_api_authenticated, expected_tracks_content): """Test get_sub_30_sec_streams_to_json.""" res = spotify_api_authenticated.get_sub_30_sec_streams_to_json( '2017-10-01', 'AT') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'sub_30_sec_streams', spotify_api_authenticated.licensor_name) def test_sub_30_sec_streams_to_json_date( mock_session, spotify_api_authenticated, mock_tracks_content, expected_tracks_content): """Test get_streams_to_json with date argument.""" d = datetime.date(2017, 10, 1) res = spotify_api_authenticated.get_sub_30_sec_streams_to_json(d, 'AT') assert_get_to_json( res, expected_tracks_content, mock_session, '2017/10/01', 'sub_30_sec_streams', spotify_api_authenticated.licensor_name) def test_get_partitions_for_url(spotify_api): """Test get_partitions_for_url.""" with patch.object(spotify_api, '_query_api') as query_api_mock: query_api_mock.return_value.text = '{"a": "2"}\n{"a":"3"}' result = spotify_api.get_partitions_for_url(url=MagicMock()) assert result == [{'a': '2'}, {'a': '3'}] def test_get_available_countries_for_url(spotify_api): """Test get_available_countries_for_url.""" with patch.object(spotify_api, '_query_api') as query_api_mock: query_api_mock.return_value.json.return_value = [ {'description': 'AA', 'uri': 'https://...'}, {'description': 'AB', 'uri': 'https://...'}, ] result = spotify_api.get_available_countries_for_url(url=MagicMock()) assert result == ['AA', 'AB'] @patch('feed_ingestion.flows.spotify.spotify_api.Session') def test_query_api(session_mock, spotify_api): """Test _query_api.""" url = 'https://provider-api.spotify.com/something' with patch.object(spotify_api, '_get_auth_header_for_request'): result = spotify_api._query_api(url) assert session_mock.return_value.get.return_value.raise_for_status.called assert session_mock.return_value.mount.call_args_list == [ call('https://', ANY) ] assert result == session_mock.return_value.get.return_value def test_query_api_should_fail(spotify_api): """Test _query_api incorrect url.""" url = 'https://theorchrd.com' with pytest.raises(AssertionError): spotify_api._query_api(url)