from datetime import date, timedelta from typing import Any, Dict, List, Tuple from unittest.mock import AsyncMock from aiohttp.test_utils import TestClient from http import HTTPStatus as http_status import pytest from apollo_utils.core.constants import ALL from pytest_mock import MockerFixture from server.client.clients.apollo_api import ApolloApiClient from server.client.clients.dsp_api import DspApiClient from server.client.clients.vendor_api import VendorApiClient from server.client.utils import str_to_date from server.constants import DSP, DSP_SPOTIFY_APPLE_AMAZON from server.constants.charts import ChartType from server.constants.tracks import TracksGTPInclude, TracksTopMarketsTiktokInclude from tests.api.charts.test_charts import get_chart_date from tests.api.tracks.helpers.visited_tracks import VisitedTracksResponseFabricator from tests.data.base.distributors import get_tracks_distributors_map_mock @pytest.mark.parametrize( "params,tracks,in_charts,weeks,re_entry,vendor_data,status,called_count,result", ( ({}, [], [], {}, [], [], http_status.BAD_REQUEST, (0, 0, 0), []), ({"type_id": 2}, [], [], {}, [], [], http_status.BAD_REQUEST, (0, 0, 0), []), ({"date": "2021-02-05"}, [], [], {}, [], [], http_status.BAD_REQUEST, (0, 0, 0), []), ( {"type_id": 2, "date": "2021-02-05", "market": "us"}, [{"id": "tr1", "isrc": "i1"}, {"id": "tr2", "isrc": "i2"}], ["i2"], {"i2": 3}, ["i1"], [{"id": "tr1", "f1": "t"}], http_status.OK, (1, 1, 1), [ { "id": "tr1", "isrc": "i1", "data": {"id": "tr1", "f1": "t"}, "weeks": 0, "in_chart": False, "is_re_enter": False, }, {"id": "tr2", "isrc": "i2", "data": None, "weeks": 0, "in_chart": True, "is_re_enter": False}, ], ), ( {"type_id": 3, "date": "2021-02-06", "market": "us", "include": ALL}, [{"id": "tr1", "isrc": "i1"}, {"id": "tr2", "isrc": "i2"}], ["i2"], {"i2": 3}, ["i1"], [{"id": "tr1", "f1": "t"}], http_status.OK, (4, 3, 1), [ { "id": "tr1", "isrc": "i1", "data": {"id": "tr1", "f1": "t"}, "weeks": 0, "in_chart": False, "is_re_enter": True, }, {"id": "tr2", "isrc": "i2", "data": None, "weeks": 3, "in_chart": True, "is_re_enter": False}, ], ), ( { "type_id": 4, "date": "2021-02-06", "market": "us", "include": f"{TracksGTPInclude.WEEKS.value},{TracksGTPInclude.RE_ENTER.value}", }, [{"id": "tr3", "isrc": "i3"}, {"id": "tr4", "isrc": "i4"}], ["i3"], {"i3": 2, "i4": 1}, ["i4"], [{"id": "tr3", "f1": "t1"}, {"id": "tr4", "f2": "t2"}], http_status.OK, (3, 1, 1), [ { "id": "tr3", "isrc": "i3", "data": {"id": "tr3", "f1": "t1"}, "weeks": 2, "in_chart": True, "is_re_enter": False, }, { "id": "tr4", "isrc": "i4", "data": {"id": "tr4", "f2": "t2"}, "weeks": 1, "in_chart": False, "is_re_enter": True, }, ], ), ), ) async def test_get_tracks_gtp( params: dict, tracks: List[dict], in_charts: List[str], weeks: Dict[str, int], re_entry: List[str], vendor_data: List[dict], status: int, called_count: Tuple[int, int, int], result: List[dict], mocker: MockerFixture, auth: dict, client, ): chart_date = "2021-02-02" hot_hits_date = "2021-02-03" streams_date = "2021-02-01" gtp_date = "2021-02-04" async def apollo_send(url: str, *args, **kwargs): if url == "api/gtp/tracks/history/": return {"gtp_date": gtp_date, "tracks": tracks} elif url == "api/gtp/tracks/weeks/": return weeks elif url == "api/gtp/hot-hits/latest-date/": return hot_hits_date elif url == "api/gtp/tracks/re-entry/": return re_entry async def dsp_send(url: str, *args, **kwargs): if url == "api/delphi/spotify/charts/data-health/status": return get_chart_date(market_list=[params["market"]], chart_date=chart_date) elif url == "api/delphi/spotify/tracks/charts/lifetime": return {"items": [{"public_meta": {"isrc": isrc}} for isrc in in_charts]} elif url == "api/consumer_analytics/streams-latest-date": return {"date": streams_date} include = params.get("include", {}) include_dates = TracksGTPInclude.DATES.value in include or ALL in include apollo_send_mock = mocker.patch.object(ApolloApiClient, "_send_request", side_effect=apollo_send) dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_send) vendor_mock = AsyncMock(return_value={"tracks": vendor_data}) vendor_send_mock = mocker.patch.object(VendorApiClient, "_send_request", side_effect=vendor_mock) response = await client.get("/api/tracks/gtp/", params=params, headers=auth) assert response.status == status assert apollo_send_mock.call_count == called_count[0] assert dsp_send_mock.call_count == called_count[1] assert vendor_send_mock.call_count == called_count[2] if status == http_status.OK: response = await response.json() response["tracks"] = sorted(response["tracks"], key=lambda x: x["id"]) assert response == { "tracks": result, "chart_date": chart_date if include_dates else None, "gtp_date": gtp_date, "hot_hits_date": hot_hits_date if include_dates else None, "streams_date": streams_date if include_dates else None, } @pytest.mark.parametrize( "params,dsp_data,status,result", ( ({"isrc": "AA1,BB2", "date": "2021-02-11"}, None, http_status.BAD_REQUEST, None), ({"market": "us", "date": "2021-02-11"}, None, http_status.BAD_REQUEST, None), ({"market": "us", "isrc": "AA1,BB2"}, None, http_status.BAD_REQUEST, None), ( {"market": "us", "isrc": "AA1,BB2", "date": "2021-02-11"}, [ {"isrc": "AA1", "data": [ {"countryCode": "us", "data": [ {"date": "2021-02-10", "streams": 1050}, {"date": "2021-02-09", "streams": 3020}, {"date": "2021-02-08", "streams": 2040}, {"date": "2021-02-03", "streams": 2040}, ]}, ]}, {"isrc": "BB2", "data": [ {"countryCode": "us", "data": [ {"date": "2021-02-10", "streams": 1200}, {"date": "2021-02-09", "streams": 1500}, {"date": "2021-02-04", "streams": 1800}, {"date": "2021-02-03", "streams": 900}, {"date": "2021-02-02", "streams": 750}, ]}, ]}, ], http_status.OK, [ {"isrc": "AA1", "data": [ {"date": "2021-02-03", "streams": 2040}, {"date": "2021-02-10", "streams": 1050} ]}, {"isrc": "BB2", "data": [ {"date": "2021-02-03", "streams": 900}, {"date": "2021-02-10", "streams": 1200} ]}, ], ), ( {"market": "us", "isrc": "AA1,BB2", "date": "2021-02-11", "date_shift": 2}, [ {"isrc": "AA1", "data": [ {"countryCode": "us", "data": [ {"date": "2021-02-10", "streams": 1050}, {"date": "2021-02-09", "streams": 3020}, {"date": "2021-02-08", "streams": 2040}, {"date": "2021-02-03", "streams": 2040}, ]}, ]}, ], http_status.OK, [ {"isrc": "AA1", "data": [ {"date": "2021-02-08", "streams": 2040}, {"date": "2021-02-10", "streams": 1050} ]}, ], ), ( {"market": "us", "isrc": "AA1,BB2", "date": "2021-02-09", "date_shift": 2}, [ {"isrc": "AA1", "data": [ {"countryCode": "us", "data": [ {"date": "2021-02-10", "streams": 1050}, {"date": "2021-02-09", "streams": 3020}, {"date": "2021-02-08", "streams": 2040}, {"date": "2021-02-03", "streams": 2040}, ]}, ]}, ], http_status.OK, [{"isrc": "AA1", "data": [{"date": "2021-02-09", "streams": 3020}]}], ), ), ) async def test_get_tracks_streams_by_date_shift(params, dsp_data, status, result, mocker, auth, client): latest_date = "2021-02-10" async def dsp_send(url: str, *args, **kwargs): if url == "api/consumer_analytics/streams-latest-date": return {"date": latest_date} return dsp_data dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_send) response = await client.get("/api/tracks/streams/by-date-shift/", params=params, headers=auth) assert response.status == status assert dsp_send_mock.call_count == int(status == http_status.OK) * 2 if status == http_status.OK: response = await response.json() response = sorted(response, key=lambda x: x["isrc"]) for item in response: item["data"] = sorted(item["data"], key=lambda x: x["date"]) assert response == result def get_tracks_playlists(count: int = 3, streams: List[int] = None) -> List[dict]: if not count: count = len(streams) return [ { "added_date": f"2020-05-{i:02d}", "country_code": "us", "current_position": 10 + i, "id": f"p{i}", "image_url": f"https://dhfsk7jl2g0km.cloudfront.net/p{i}.jpeg", "name": f"playlist {i}", "personalized": bool(i % 10), "position_change_date": None, "track_id": "tr1", "trend": 50 + i, "updated_date": f"2021-05-{i:02d}", "is_new": bool((i + 1) % 10), "playlist_streams": 10000 + i, "followers": 1000 + i, "username": "user", "track_count": 30 * i, **({"track_streams": streams[i - 1]} if streams else {}), } for i in range(1, count + 1) ] @pytest.mark.parametrize( "params,apollo_data,dsp_data,status,dsp_called,result,sort_field", ( ({"isrc": ["A1", "B2"]}, None, None, http_status.BAD_REQUEST, 0, None, None), ({"vendor": "spotify"}, None, None, http_status.BAD_REQUEST, 0, None, None), ( {"isrc": ["A1"], "vendor": "spotify", "include": "playlist_streams", "order_by": "track_streams"}, None, None, http_status.BAD_REQUEST, 0, None, None, ), ( {"isrc": ["A1", "B2"], "vendor": "spotify"}, get_tracks_playlists(), None, http_status.OK, 0, get_tracks_playlists(), "added_date", ), ( {"isrc": ["A1", "B2"], "vendor": "spotify", "order_by": "-playlist_streams", "include": "playlist_streams"}, get_tracks_playlists(), None, http_status.OK, 0, get_tracks_playlists(), "-playlist_streams", ), ( {"isrc": ["A1", "B2"], "vendor": "apple", "limit": "2", "order_by": "id"}, get_tracks_playlists(), None, http_status.OK, 0, get_tracks_playlists(count=2), "id", ), ( {"isrc": ["A1", "B2"], "vendor": "apple", "limit": "2", "offset": 1, "order_by": "-id"}, get_tracks_playlists(), None, http_status.OK, 0, get_tracks_playlists(count=2), "-id", ), ( {"isrc": "A1", "vendor": "apple", "include": ["track_streams", "username"], "order_by": "-track_streams"}, get_tracks_playlists(), [ {"playlist_id": "p1", "streams": 1040}, {"playlist_id": "p2", "streams": 2050}, {"playlist_id": "p3", "streams": 6432}, ], http_status.OK, 2, get_tracks_playlists(streams=[1040, 2050, 6432]), "-track_streams", ), ( { "isrc": "A1", "vendor": "apple", "include": ["track_streams", "followers", "track_count"], "order_by": ["-track_streams", "-followers"], }, get_tracks_playlists(), [ {"playlist_id": "p1", "streams": 1040}, {"playlist_id": "p2", "streams": 2050}, {"playlist_id": "p3", "streams": 6432}, ], http_status.OK, 2, get_tracks_playlists(streams=[1040, 2050, 6432]), "-track_streams,-username", ), ), ) async def test_get_tracks_playlists( params, apollo_data, dsp_data, status, dsp_called, result, sort_field, mocker, auth, client ): latest_date = "2021-02-12" async def dsp_send(url: str, *args, **kwargs): if url == "api/consumer_analytics/streams-latest-date": return {"date": latest_date} return dsp_data dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_send) apollo_mock = AsyncMock( return_value={ "items": apollo_data, "count": len(apollo_data) if apollo_data else 0, "next": None, "previous": None, } ) apollo_send_mock = mocker.patch.object(ApolloApiClient, "_send_request", side_effect=apollo_mock) response = await client.get("/api/tracks/playlists/", params=params, headers=auth) assert response.status == status assert apollo_send_mock.call_count == int(status == http_status.OK) assert dsp_send_mock.call_count == dsp_called if status == http_status.OK: response = (await response.json())["items"] reverse = sort_field.startswith("-") fields = [i.lstrip("-") for i in sort_field.split(",")] result = sorted(result, key=lambda x: tuple(x[i] for i in fields), reverse=reverse) assert response == result def get_nmf_tracklist(count: int = 5, dist_to_ids=None, track_meta_idxs=None) -> List[dict]: track_meta_idxs = track_meta_idxs or [] id_to_dist = {_id: dist for dist, tracks in dist_to_ids.items() for _id in tracks} if dist_to_ids else {} return [ { "trackId": f"tr_{i}", **( {"data": {"f": str(i)}} if dist_to_ids is None else { "trackName": f"tr_name_{i}" if i in track_meta_idxs else None, "isSony": id_to_dist.get(i) == "sme", "distributed_by": id_to_dist.get(i), "artists": [ {"id": f"ar_{j}", "name": f"ar_name_{j}"} for j in range(10, i + 9) ] if i in track_meta_idxs else [], "albumImageUrl": f"url_{i + 20}" if i > 1 and i in track_meta_idxs else None, "sonyRelease": None, } ) } for i in range(1, count + 1) ] def get_track_meta_list(count: int = 4, offset: int = 1, limit: int = 3): return [ { "id": f"tr_{i}", "name": f"tr_name_{i}", "artists": [{"id": f"ar_{j}", "name": f"ar_name_{j}"} for j in range(10, i + 9)], "album": {"images": [{"width": j, "url": f"url_{i + j}"} for j in range(20, i + 19)]} } for i in range(offset, min(limit, count) + 1) ] @pytest.mark.parametrize( "params,expected_status,apollo_data,distributors_tracks,expected_result,distributors_called", ( ({"market": "us"}, http_status.BAD_REQUEST, [], [], [], 0), ({"date": "2021-05-05"}, http_status.OK, [], ({}, {}), [], 0), ( {"date": "2021-05-05"}, http_status.OK, get_nmf_tracklist(), get_tracks_distributors_map_mock( dist_to_ids={"sme": (2,)}, remove_result_dsp_prefix=True, extra_data={"track_list": get_track_meta_list(offset=2, limit=4)}, track_id_func=lambda x: f"tr_{x}" ), get_nmf_tracklist(dist_to_ids={"sme": (2,)}, track_meta_idxs=[2, 3, 4]), 1, ), ( {"date": "2021-05-01", "market": "us", "distributors": ["sme", "awal"]}, http_status.OK, get_nmf_tracklist(), get_tracks_distributors_map_mock( dist_to_ids={"sme": (2, 4), "awal": (4,)}, remove_result_dsp_prefix=True, extra_data={"track_list": get_track_meta_list(offset=2, limit=4)}, track_id_func=lambda x: f"tr_{x}" ), get_nmf_tracklist(dist_to_ids={"sme": (2, 4), "awal": (4,)}, track_meta_idxs=[2, 3, 4]), 1, ), ( {"date": "2021-05-10"}, http_status.OK, get_nmf_tracklist(), get_tracks_distributors_map_mock( dist_to_ids={}, remove_result_dsp_prefix=True, extra_data={"track_list": get_track_meta_list(offset=1, limit=3)}, track_id_func=lambda x: f"tr_{x}" ), get_nmf_tracklist(dist_to_ids={}, track_meta_idxs=[1, 2, 3]), 1, ), ), ) async def test_get_nmf_tracklist( params: dict, expected_status: int, apollo_data: List[dict], distributors_tracks: Tuple[dict, dict], expected_result: List[dict], distributors_called: int, mocker: MockerFixture, auth: dict, client, ): apollo_mock = AsyncMock(return_value={"items": apollo_data}) apollo_send_mock = mocker.patch.object(ApolloApiClient, "_send_request", side_effect=apollo_mock) distributors_mock = AsyncMock(return_value=distributors_tracks) distributors_func_mock = mocker.patch( "server.api.tracks.misc.get_tracks_distributors_map", side_effect=distributors_mock ) response = await client.get("/api/tracks/nmf/", params=params, headers=auth) assert response.status == expected_status ok_status = int(expected_status == http_status.OK) assert apollo_send_mock.call_count == ok_status assert distributors_func_mock.call_count == distributors_called if ok_status: response = await response.json() assert response["items"] == expected_result def get_streams(isrc_list: List[str], dsp_list: List[str]) -> List[Dict[str, str or int]]: if not dsp_list: dsp_list = list(DSP_SPOTIFY_APPLE_AMAZON.values()) elif DSP.TIKTOK.value in dsp_list: dsp_list = list(dsp_list) dsp_list.remove(DSP.TIKTOK.value) result = [] for k, isrc in enumerate(isrc_list): for i, dsp in enumerate(dsp_list): for j, market in enumerate(("worldwide", "us", "ca", "gb", "it", "dk")): result.append({ "country_code": market, "dsp": dsp, "isrc": isrc, "streams": 50000 - (k + 1) * 2000 + (1 - i) * 1000 + (3 - j) * 100, }) return result def get_tiktok_analytics(isrc_list: List[str], metrics: List[str]) -> Dict[str, Any]: result = {} for i, isrc in enumerate(isrc_list): for j, market in enumerate(("worldwide", "us", "ca", "gb", "it", "dk")): result[market] = { **( {"creations": 500 + (1 - i) * 20 + (3 - j) * 5} if TracksTopMarketsTiktokInclude.CREATIONS.value in metrics else {} ), **( {"video_views": 5000 + (1 - i) * 100 + (3 - j) * 10} if TracksTopMarketsTiktokInclude.VIDEO_VIEWS.value in metrics else {} ), } return { "min_date": "2021-07-12", "max_date": "2021-07-18", "days_count": 7, "breakdowns": {"country_totals": result} } @pytest.mark.parametrize( "params,expected_status,expected_result,call_count", ( ({}, http_status.BAD_REQUEST, {}, 0), ({"start_date": "2021-08-01", "end_date": "2021-08-03"}, http_status.BAD_REQUEST, {}, 0), ({"isrc": "isrc_1,isrc_2"}, http_status.BAD_REQUEST, {}, 0), ({"dsp": "spotify"}, http_status.BAD_REQUEST, {}, 0), ( {"isrc": "isrc_1,isrc_2", "dsp": "spotify", "start_date": "2021-08-05", "end_date": "2021-08-03"}, http_status.BAD_REQUEST, {}, 0, ), ( {"isrc": "isrc_1,isrc_2", "dsp": "spotify", "start_date": "2021-08-01", "end_date": "2021-08-03"}, http_status.OK, { "spotify": [ {"market": "us", "streams": 96400}, {"market": "ca", "streams": 96200}, {"market": "gb", "streams": 96000}, {"market": "it", "streams": 95800}, {"market": "dk", "streams": 95600}, ], }, 1, ), ( {"isrc": "isrc_1", "dsp": "spotify", "start_date": "2021-08-01", "end_date": "2021-08-03", "limit": 2}, http_status.OK, {"spotify": [{"market": "us", "streams": 49200}, {"market": "ca", "streams": 49100}]}, 1, ), ( {"isrc": "isrc_1", "dsp": "tiktok", "start_date": "2021-08-01", "end_date": "2021-08-03", "limit": 2}, http_status.OK, {"tiktok": [{"market": "us", "video_views": 5120}, {"market": "ca", "video_views": 5110}]}, 1, ), ( { "isrc": "isrc_1", "dsp": "tiktok", "start_date": "2021-08-01", "end_date": "2021-08-03", "limit": 2, "metrics": "video_views,creations", }, http_status.OK, { "tiktok": { "video_views": [{"market": "us", "video_views": 5120}, {"market": "ca", "video_views": 5110}], "creations": [{"market": "us", "creations": 530}, {"market": "ca", "creations": 525}], }, }, 1, ), ( { "isrc": "isrc_1,isrc_2", "dsp": "spotify,apple,amazon,tiktok", "start_date": "2021-08-01", "end_date": "2021-08-03", "include_worldwide": "true", "limit": 2, }, http_status.OK, { "amazon": [{"market": "worldwide", "streams": 92600}, {"market": "us", "streams": 92400}], "apple": [{"market": "worldwide", "streams": 94600}, {"market": "us", "streams": 94400}], "spotify": [{"market": "worldwide", "streams": 96600}, {"market": "us", "streams": 96400}], "tiktok": [{"market": "worldwide", "video_views": 5030}, {"market": "us", "video_views": 5020}], }, 2, ), ( { "isrc": "isrc_1,isrc_2", "dsp": "spotify,apple,amazon", "start_date": "2021-08-01", "end_date": "2021-08-03", "include_worldwide": "true", "combine": "true", "limit": 2, }, http_status.OK, {"all": [{"market": "worldwide", "streams": 283800}, {"market": "us", "streams": 283200}]}, 1, ), ( { "isrc": "isrc_1,isrc_2", "dsp": "spotify,apple,amazon,tiktok", "start_date": "2021-08-01", "end_date": "2021-08-03", "include_worldwide": "true", "combine": "true", "limit": 2, }, http_status.OK, {"all": [{"market": "worldwide", "streams": 288830}, {"market": "us", "streams": 288220}]}, 2, ), ( { "isrc": "isrc_1,isrc_2", "dsp": "spotify,apple,amazon,tiktok", "start_date": "2021-08-01", "end_date": "2021-08-03", "include_worldwide": "true", "combine": "true", "limit": 2, "metrics": "video_views,creations", }, http_status.OK, {"all": [{"market": "worldwide", "streams": 289345}, {"market": "us", "streams": 288730}]}, 2, ), ( { "isrc": "isrc_1,isrc_2", "dsp": "spotify,amazon", "start_date": "2021-08-01", "end_date": "2021-08-05", "combine": "true", "limit": 3, }, http_status.OK, { "all": [ {"market": "us", "streams": 190800}, {"market": "ca", "streams": 190400}, {"market": "gb", "streams": 190000}, ], }, 1, ), ), ) async def test_get_tracks_markets_top( params: dict, expected_status: int, expected_result: Dict[str, List[dict]], call_count: int, mocker: MockerFixture, auth: dict, client: TestClient, ): async def dsp_send(url: str, *args, **kwargs): isrc_list = params.get("isrc", "").split(",") if url == "api/delphi/tiktok/tracks/analytics": return get_tiktok_analytics( isrc_list, params.get("metrics", TracksTopMarketsTiktokInclude.VIDEO_VIEWS.value).split(","), ) else: return get_streams(isrc_list, params.get("dsp", "").split(",")) dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_send) response = await client.get("/api/tracks/markets/top/", params=params, headers=auth) assert response.status == expected_status assert dsp_send_mock.call_count == call_count if call_count: response = await response.json() assert response == expected_result @pytest.mark.parametrize( "user_id,market,status,data", ( ("user_1", "us", http_status.OK, True), ("user_1", None, http_status.OK, True), ("user_1", None, http_status.OK, False), ) ) async def test_most_visited_tracks(auth, client, mocker, user_id, market, status, data): params = {"market": market} if market else {} if data: response_fabricator = VisitedTracksResponseFabricator() user_visits, tracks, artists, dsp_tracks, is_sony = response_fabricator.get_dependant_responses() user_visits_copy, track_copy, artists_copy, dsp_tracks_copy = \ response_fabricator.get_deepcopy(user_visits, tracks, artists, dsp_tracks) expected_response = response_fabricator.get_expected_spotify_response( user_visits, tracks, dsp_tracks, artists, is_sony ) else: user_visits_copy, expected_response = [], [] async def apollo_send(url: str, *args, **kwargs): vendor = kwargs["params"]["vendor"] if url == "api/visited-tracks/": if vendor == "spotify": return user_visits_copy else: return [] if url == "api/tracks/": return track_copy if url == "api/tracks/artists/": return artists_copy if url == "api/albums/is-sony/": return is_sony if url == "api/check-market/": return True async def vendor_send(url: str, *args, **kwargs): if url == "api/spotify/v1/tracks": return dsp_tracks_copy if url == "api/spotify/v1/albums": return [] mocker.patch.object(ApolloApiClient, "_send_request", side_effect=apollo_send) mocker.patch.object(VendorApiClient, "_send_request", side_effect=vendor_send) auth.update({"X-User-id": user_id}) if user_id else auth response = await client.get("/api/user/tracks/visited/", params=params, headers=auth) assert response.status == status response_json = await response.json() assert response_json == expected_response class ChartBlock: CHART_META = "chart_meta" METRICS = "metrics" LIFETIME_METRICS = "lifetime_metrics" def get_country_code(index: int) -> str: return f"m{index}" def get_markets(item_list: List[int] = None, count: int = 10) -> List[dict]: if not item_list: item_list = list(range(1, count + 1)) return [ { "id": 10 + index, "code": get_country_code(index), "full_name": f"FullMar_{index}", "name": f"Mar_{index}", "region": {"name": f"Reg_{index}", "code": f"r{index % 10}", "rank": 20 + index}, } for index in item_list ] def get_tracks_charts( item_list: List[int] = None, count: int = 20, dsp: str = DSP.SPOTIFY.value, breakdown: str = "daily", current_date: date = date(2022, 5, 10), include_list: List[str] = None, metrics_index_list: List[int] = None, lifetime_metrics_index_list: List[int] = None, is_plane_chart: bool = False, region_index_list: List[int] = None, is_result: bool = False, ) -> dict: if not item_list: item_list = list(range(1, count + 1)) is_spotify = dsp == DSP.SPOTIFY.value result = [] for index in item_list: country_code = get_country_code(index) chart_type = ChartType.REGIONAL.value if is_spotify else "charts" is_entry = index % 3 == 0 has_region = region_index_list and index in region_index_list chart_meta = { "chart_id": f"{chart_type}_{breakdown}_{country_code}", "name": ( f"Mar_{index}" if has_region else f"{dsp.capitalize()} Top {breakdown.capitalize()} {chart_type} {country_code}" ), "total_positions": 200 if is_spotify else 100, "country_code": country_code, "rank": 100 - index, **({"type": ChartType.REGIONAL.value, "breakdown": breakdown} if is_spotify else {}), **( {"region": {"name": f"Reg_{index}", "code": f"r{index % 10}", "rank": 20 + index}} if has_region else {} ), } if is_plane_chart: result.append(chart_meta) continue result_item = {} if include_list and ChartBlock.CHART_META in include_list: result_item["chart_meta"] = chart_meta if include_list and ChartBlock.METRICS in include_list or metrics_index_list and index in metrics_index_list: result_item["metrics"] = { "date": current_date.isoformat(), "previous_date": (current_date - timedelta(days=index % 2 + 1)).isoformat() if is_entry else None, "is_entry": is_entry, "position": 50 + index * (-1 if index % 3 else 1), "isrc_position": (40 if index % 5 == 0 else 50) + index * (-1 if index % 3 else 1), "previous_position": 60 + index * (-1 if index % 3 == 0 else 1), "trend": -10 + index * (-1 if index % 3 else 1) - index * (-1 if index % 3 == 0 else 1), **({"date_streams": 120 + index} if is_spotify else {}), } elif is_result: result_item["metrics"] = None if ( include_list and ChartBlock.LIFETIME_METRICS in include_list or lifetime_metrics_index_list and index in lifetime_metrics_index_list ): result_item["lifetime_metrics"] = { "earliest_position": 20 + index, "earliest_position_date": f"2022-03-2{index % 10}", "latest_position": 10 + index, "latest_position_date": f"2022-07-0{index % 9 + 1}", "max_position": 50 + index, "max_position_date": f"2022-05-1{index % 10}", "min_position": 3 + index, "min_position_date": f"2022-04-2{index % 10}", "total_days": 103 + index % 9 - index % 10, } elif is_result: result_item["lifetime_metrics"] = None result.append(result_item) return {"items": result} @pytest.mark.parametrize( "params,expected_status", ( ({}, http_status.BAD_REQUEST), ({"isrc": "isrc123"}, http_status.BAD_REQUEST), ({"dsp": "spotify"}, http_status.BAD_REQUEST), ({"isrc": "isrc123", "dsp": "spotify"}, http_status.OK), ({"isrc": "isrc123", "dsp": "apple", "date": "2022-03-25", "type": "weekly"}, http_status.OK), ), ) async def test_get_tracks_charts( params: dict, expected_status: int, mocker: MockerFixture, auth: dict, client: TestClient ): dsp, current_date, breakdown = ( params.get("dsp"), str_to_date(params.get("date", "2022-03-22")), params.get("type", "daily") ) lifetime_index = current_date.day - 20 is_date_none = "date" not in params metrics_index_list = [1, 2, 3, 7, 10, 15, 16, 17, 19, 20] lifetime_metrics_index_list = [3, 4, 6, 8, 9, 10, 12, 14, 18, 19, 20] region_index_list = [1, 3, 4, 6, 7, 10, 11, 17, 18, 20] async def dsp_send(url: str, *args, **kwargs): url = url.replace("api/delphi/spotify/", "").replace("api/delphi/apple-music/", "") if url == "charts": return get_tracks_charts(dsp=dsp, breakdown=breakdown, current_date=current_date, is_plane_chart=True) elif url == "tracks/charts": return get_tracks_charts( dsp=dsp, breakdown=breakdown, current_date=current_date, include_list=[ChartBlock.CHART_META, ChartBlock.METRICS, ChartBlock.LIFETIME_METRICS], item_list=metrics_index_list, ) elif url == "tracks/charts/lifetime": return get_tracks_charts( dsp=dsp, breakdown=breakdown, current_date=current_date, include_list=[ChartBlock.CHART_META, ChartBlock.LIFETIME_METRICS], item_list=lifetime_metrics_index_list, ) dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_send) apollo_mock = AsyncMock( return_value=get_markets(item_list=region_index_list) ) apollo_send_mock = mocker.patch.object(ApolloApiClient, "_send_request", side_effect=apollo_mock) response = await client.get("/api/tracks/charts/", params=params, headers=auth) assert response.status == expected_status if expected_status == http_status.OK: response = await response.json() assert response == get_tracks_charts( dsp=dsp, breakdown=breakdown, current_date=current_date, metrics_index_list=metrics_index_list, lifetime_metrics_index_list=list( set(metrics_index_list) | set( i for i in lifetime_metrics_index_list if i % 10 < lifetime_index or is_date_none ) ), region_index_list=region_index_list, include_list=[ChartBlock.CHART_META], is_result=True, ) else: assert dsp_send_mock.call_count == 0 assert apollo_send_mock.call_count == 0