from http import HTTPStatus from typing import List, Optional from unittest import mock import pytest from aiohttp.test_utils import TestClient from pytest_mock import MockerFixture from tests.apple.fixtures.data_sets import DataTuple pytest_plugins = [ "tests.apple.fixtures.responses", "tests.apple.fixtures.data_sets", ] async def test_permissions_fail(client: TestClient, data_set: DataTuple) -> None: response = await client.get(data_set.url) assert response.status == HTTPStatus.UNAUTHORIZED async def test_permissions_success( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response response = await client.get(data_set.url, headers=auth_header) assert response.status == HTTPStatus.OK assert await response.json() == data_set.expected_response mock_apple_playlist.assert_called_once_with( data_set.playlist_id, data_set.storefront, include=None, localization=None, include_songs=None, include_music_videos=None, include_library_playlists=None, ) async def test_parameters_valid_localization( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response response = await client.get(data_set.url, headers=auth_header, params={"l": data_set.localization}) assert response.status == HTTPStatus.OK assert await response.json() == data_set.expected_response mock_apple_playlist.assert_called_once_with( data_set.playlist_id, data_set.storefront, include=None, localization=data_set.localization, include_songs=None, include_music_videos=None, include_library_playlists=None, ) @pytest.mark.parametrize( "params", [ ({"l": "localization_too_long"}), # localization too long ({"l": "a"}), # localization too short ], ) async def test_parameters_invalid_localization( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, params: dict ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response response = await client.get(data_set.url, headers=auth_header, params=params) assert response.status == HTTPStatus.BAD_REQUEST assert not mock_apple_playlist.called async def test_parameters_include( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response response = await client.get(data_set.url, headers=auth_header, params={"include": "tracks"}) assert response.status == HTTPStatus.OK mock_apple_playlist.assert_called_once() assert await response.json() == data_set.expected_response mock_apple_playlist.assert_called_once_with( data_set.playlist_id, data_set.storefront, include=["tracks"], localization=None, include_songs=None, include_music_videos=None, include_library_playlists=None, ) @pytest.mark.parametrize( "params, songs, videos, playlists", [ ({"include[songs]": "tracks"}, ["tracks"], None, None), ({"include[music-videos]": "tracks"}, None, ["tracks"], None), ({"include[library-playlists]": "tracks"}, None, None, ["tracks"]), ], ) async def test_parameters_scoped_include( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, params: dict, songs: Optional[List[str]], videos: Optional[List[str]], playlists: Optional[List[str]], ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response # TODO: actual scope can be enumerated and validated as well response = await client.get(data_set.url, headers=auth_header, params=params) assert response.status == HTTPStatus.OK assert await response.json() == data_set.expected_response mock_apple_playlist.assert_called_with( data_set.playlist_id, data_set.storefront, include=None, localization=None, include_songs=songs, include_music_videos=videos, include_library_playlists=playlists, ) @pytest.mark.parametrize( "params, include, songs, videos, playlists", [ # Case 1: one parameter per each. ( { "include": "songs", "include[songs]": "artists", "include[music-videos]": "albums", "include[library-playlists]": "tracks", }, ["songs"], ["artists"], ["albums"], ["tracks"], ), # Case 2: multiple parameters per each. Please, notice, that params order is preserved. ( { "include": "songs,artists", "include[songs]": "artists,albums", "include[music-videos]": "albums,tracks", "include[library-playlists]": "tracks,genres", }, ["songs", "artists"], ["artists", "albums"], ["albums", "tracks"], ["tracks", "genres"], ), ], ) async def test_parameters_include_multi( client: TestClient, auth_header: dict, mocker: MockerFixture, data_set: DataTuple, params: dict, include: List[str], songs: List[str], videos: List[str], playlists: List[str], ) -> None: mock_apple_playlist = mocker.patch.object(client.app["apple_api"], "playlist") mock_apple_playlist.return_value = data_set.expected_response response = await client.get(data_set.url, headers=auth_header, params=params) assert response.status == HTTPStatus.OK assert await response.json() == data_set.expected_response mock_apple_playlist.assert_called_with( data_set.playlist_id, data_set.storefront, include=include, localization=None, include_songs=songs, include_music_videos=videos, include_library_playlists=playlists, ) async def test_handler_client_integration( client: TestClient, auth_header: dict, mocker: MockerFixture, apple_playlist_response: dict, data_set: DataTuple, ) -> None: mock_apple_make_request = mocker.patch("server.apple.client.make_request") mock_apple_make_request.return_value = apple_playlist_response mock_apple_get_token = mocker.patch.object(client.app["apple_api"], "generate_token") mock_apple_get_token.return_value = "test_token" request_params = { "include": "songs,artists", "include[songs]": "artists,albums", "include[music-videos]": "albums,tracks", "include[library-playlists]": "tracks,genres", } response = await client.get( data_set.url, headers=auth_header, params=request_params, ) assert response.status == HTTPStatus.OK assert await response.json() == apple_playlist_response mock_apple_make_request.assert_called_once_with( mock.ANY, # client session object f"https://api.music.apple.com/v1/catalog/{data_set.storefront}/playlists/{data_set.playlist_id}", params=request_params, headers=mock.ANY, # Authentication header with Bearer token, that is generated by client object error_cls=mock.ANY, # Exception class to raise. )