from datetime import datetime, timedelta from typing import List from unittest.mock import AsyncMock from http import HTTPStatus import pytest from pytest_mock import MockerFixture from server.client.clients.dsp_api import DspApiClient from server.client.clients.vendor_api import VendorApiClient @pytest.mark.parametrize( "params,min_date,max_date,positions,expected_status,expected_result", ( ({}, None, None, [], HTTPStatus.BAD_REQUEST, {}), ({"market": "us"}, None, None, [], HTTPStatus.BAD_REQUEST, {}), ({"market": "us", "isrc": "A1"}, None, None, [], HTTPStatus.BAD_REQUEST, {}), ({"start_date": "2022-01-10", "end_date": "2022-01-30"}, None, None, [], HTTPStatus.BAD_REQUEST, {}), ( { "market": "us", "isrc": "A1", "start_date": "2022-01-10", "end_date": "2022-01-30", "type": "creations", "days": "7", }, "2022-01-12", "2022-01-18", [20, 11, None, 5, 12, None, 33], HTTPStatus.OK, { "days_on_list": 5, "end_date": "2022-01-18", "items": [ {"date": "2022-01-12", "value": 20}, {"date": "2022-01-13", "value": 11}, {"date": "2022-01-15", "value": 5}, {"date": "2022-01-16", "value": 12}, {"date": "2022-01-18", "value": 33}], "min_position": 33, "peak_position": 5, "start_date": "2022-01-12", "total_tracks_in_chart": 50, }, ), ), ) async def test_get_tracks_tiktok_positions( params: dict, min_date: str, max_date: str, positions: List[int], expected_status: int, expected_result: dict, mocker: MockerFixture, auth: dict, client, ): positions_not_none = [i for i in positions if i is not None] dsp_response = { "min_date": min_date, "max_date": max_date, "min_position": min(positions_not_none) if positions_not_none else None, "max_position": max(positions_not_none) if positions_not_none else None, "metrics": {"positions": positions}, } dsp_mock = AsyncMock(return_value=dsp_response) dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_mock) response = await client.get("/api/charts/tracks/tiktok/positions/", params=params, headers=auth) assert response.status == expected_status ok_status = int(expected_status == HTTPStatus.OK) assert dsp_send_mock.call_count == ok_status if ok_status: response = await response.json() assert response == expected_result def get_charts(count: int = 5, chart_date_str: str = "2022-02-10") -> dict: chart_date = datetime.fromisoformat(chart_date_str).date() return { "count": count, "items": [ { "metrics": { "date": chart_date_str, "previous_date": ( (chart_date - timedelta(days=i * 2)).isoformat() if i % 3 == 0 else (None if i % 4 == 0 else (chart_date - timedelta(days=1)).isoformat()) ), "is_entry": i % 4 == 0, "position": i, "creations": round(2573 / i) if i < 3 else 2421 * i, "previous_position": None if i % 4 == 0 else 20 - i * 2, }, "aggregated_metrics": { "last_7day_top_markets": [f"m{j}" for j in range(i * 2, i * 2 + 7)], "previous_1day_creations": 1532 * i if i < 3 else round(3324 / i), "previous_week_7day_creations": 32212 * i if i < 2 or i > 3 else round(4266 / i), "previous_week_1day_creations": 3271 * i if 1 < i < 3 else round(5322 / i), }, "lifetime_metrics": { "total_days": i * 5, "earliest_position_date": (chart_date - timedelta(days=20 + i * 2)).isoformat(), "latest_position_date": chart_date_str, }, "source_meta": {"song_title": f"Song {i}", "artist": f"Delphi Artist {i}", "isrc": f"isrc{i}"}, } for i in range(1, count + 1) ], } def get_tracks(start: int = 2, end: int = 5) -> dict: return { "tracks": [ { "album": { "images": [ {"height": 640, "url": f"https://i.scdn.co/image/640_{i}", "width": 640}, {"height": 300, "url": f"https://i.scdn.co/image/300_{i}", "width": 300}, ], }, "artists": [ { "external_urls": {"spotify": f"https://open.spotify.com/artist/ar{i}"}, "href": f"https://api.spotify.com/v1/artists/ar{i}", "id": f"ar{i}", "name": f"Artist {i}", "type": "artist", "uri": f"spotify:artist:ar{i}" } ], "external_ids": {"isrc": f"isrc{i}"}, "id": f"tr{i}", "name": f"Track {i}", } for i in range(start, end) ], } @pytest.mark.parametrize( "params,chart_data,track_data,expected_status,expected_result", ( ({}, None, None, HTTPStatus.BAD_REQUEST, {}), ({"market": "us"}, None, None, HTTPStatus.BAD_REQUEST, {}), ( {"market": "us", "date": "2022-02-10", "days": "1", "type": "creations_change"}, get_charts(), get_tracks(), HTTPStatus.OK, [ { "added_date": "2022-01-19", "latest_date": "2022-02-10", "days_on_list": 5, "previous": {"creations": 1532, "end_date": "2022-02-09", "start_date": "2022-02-09"}, "selected": { "creations": 2573, "end_date": "2022-02-10", "position": {"is_new": False, "is_re_entry": False, "trend": -17, "value": 1}, "start_date": "2022-02-10", "change": {"is_new": False, "is_re_entry": False, "percent": 68, "value": 1041}, }, "top_markets": ["m2", "m3", "m4", "m5", "m6", "m7", "m8"], "track": {"isrc": "isrc1", "name": "Song 1", "artists": [{"name": "Delphi Artist 1"}]}, "week": {"change": {"percent": -52, "value": -2749}, "creations": 5322, "start_date": "2022-02-03"}, }, { "added_date": "2022-01-17", "latest_date": "2022-02-10", "days_on_list": 10, "previous": {"creations": 3064, "end_date": "2022-02-09", "start_date": "2022-02-09"}, "selected": { "creations": 1286, "end_date": "2022-02-10", "position": {"is_new": False, "is_re_entry": False, "trend": -14, "value": 2}, "start_date": "2022-02-10", "change": {"is_new": False, "is_re_entry": False, "percent": -58, "value": -1778}, }, "top_markets": ["m4", "m5", "m6", "m7", "m8", "m9", "m10"], "track": { "artists": [ { "external_urls": {"spotify": "https://open.spotify.com/artist/ar2"}, "href": "https://api.spotify.com/v1/artists/ar2", "id": "ar2", "name": "Artist 2", "type": "artist", "uri": "spotify:artist:ar2", }, ], "cover": "https://i.scdn.co/image/640_2", "id": "tr2", "isrc": "isrc2", "name": "Track 2", }, "week": {"change": {"percent": -80, "value": -5256}, "creations": 6542, "start_date": "2022-02-03"}, }, { "added_date": "2022-01-15", "latest_date": "2022-02-10", "days_on_list": 15, "previous": {"creations": 1108, "end_date": "2022-02-09", "start_date": "2022-02-09"}, "selected": { "creations": 7263, "end_date": "2022-02-10", "position": {"is_new": False, "is_re_entry": True, "trend": None, "value": 3}, "start_date": "2022-02-10", "change": {"is_new": False, "is_re_entry": True, "percent": 556, "value": 6155}, }, "top_markets": ["m6", "m7", "m8", "m9", "m10", "m11", "m12"], "track": { "artists": [ { "external_urls": {"spotify": "https://open.spotify.com/artist/ar3"}, "href": "https://api.spotify.com/v1/artists/ar3", "id": "ar3", "name": "Artist 3", "type": "artist", "uri": "spotify:artist:ar3", }, ], "cover": "https://i.scdn.co/image/640_3", "id": "tr3", "isrc": "isrc3", "name": "Track 3", }, "week": {"change": {"percent": 309, "value": 5489}, "creations": 1774, "start_date": "2022-02-03"}, }, { "added_date": "2022-01-13", "latest_date": "2022-02-10", "days_on_list": 20, "previous": {"creations": 831, "end_date": "2022-02-09", "start_date": "2022-02-09"}, "selected": { "creations": 9684, "end_date": "2022-02-10", "position": {"is_new": True, "is_re_entry": False, "trend": None, "value": 4}, "start_date": "2022-02-10", "change": {"is_new": True, "is_re_entry": False, "percent": 1065, "value": 8853}, }, "top_markets": ["m8", "m9", "m10", "m11", "m12", "m13", "m14"], "track": { "artists": [ { "external_urls": {"spotify": "https://open.spotify.com/artist/ar4"}, "href": "https://api.spotify.com/v1/artists/ar4", "id": "ar4", "name": "Artist 4", "type": "artist", "uri": "spotify:artist:ar4", }, ], "cover": "https://i.scdn.co/image/640_4", "id": "tr4", "isrc": "isrc4", "name": "Track 4", }, "week": {"change": {"percent": 628, "value": 8354}, "creations": 1330, "start_date": "2022-02-03"}, }, { "added_date": "2022-01-11", "latest_date": "2022-02-10", "days_on_list": 25, "previous": {"creations": 665, "end_date": "2022-02-09", "start_date": "2022-02-09"}, "selected": { "creations": 12105, "start_date": "2022-02-10", "end_date": "2022-02-10", "position": {"is_new": False, "is_re_entry": False, "trend": -5, "value": 5}, "change": {"is_new": False, "is_re_entry": False, "percent": 1720, "value": 11440}, }, "top_markets": ["m10", "m11", "m12", "m13", "m14", "m15", "m16"], "track": {"isrc": "isrc5", "name": "Song 5", "artists": [{"name": "Delphi Artist 5"}]}, "week": {"change": {"percent": 1038, "value": 11041}, "creations": 1064, "start_date": "2022-02-03"}, }, ], ), ( {"market": "us", "date": "2022-02-06", "days": "7", "type": "creations", "image_size": "300"}, get_charts(chart_date_str="2022-02-06", count=1), get_tracks(start=1, end=2), HTTPStatus.OK, [ { "added_date": "2022-01-15", "latest_date": "2022-02-06", "days_on_list": 5, "previous": {"creations": 32212, "end_date": "2022-01-30", "start_date": "2022-01-24"}, "selected": { "creations": 2573, "end_date": "2022-02-06", "position": {"is_new": False, "is_re_entry": False, "trend": -17, "value": 1}, "start_date": "2022-01-31", "change": {"is_new": False, "is_re_entry": False, "percent": -92, "value": -29639}, }, "top_markets": ["m2", "m3", "m4", "m5", "m6", "m7", "m8"], "track": { "artists": [ { "external_urls": {"spotify": "https://open.spotify.com/artist/ar1"}, "href": "https://api.spotify.com/v1/artists/ar1", "id": "ar1", "name": "Artist 1", "type": "artist", "uri": "spotify:artist:ar1", }, ], "cover": "https://i.scdn.co/image/300_1", "id": "tr1", "isrc": "isrc1", "name": "Track 1", }, }, ], ), ), ) async def test_get_tracks_tiktok_charts( params: dict, chart_data: dict, track_data: dict, expected_status: int, expected_result: dict, mocker: MockerFixture, auth: dict, client, ): dsp_mock = AsyncMock(return_value=chart_data) dsp_send_mock = mocker.patch.object(DspApiClient, "_send_request", side_effect=dsp_mock) vendor_mock = AsyncMock(return_value=track_data) vendor_send_mock = mocker.patch.object(VendorApiClient, "_send_request", side_effect=vendor_mock) response = await client.get("/api/charts/tracks/tiktok/", params=params, headers=auth) assert response.status == expected_status ok_status = int(expected_status == HTTPStatus.OK) assert dsp_send_mock.call_count == ok_status assert vendor_send_mock.call_count == ok_status if ok_status: response = await response.json() assert sorted(response, key=lambda i: i["track"]["isrc"]) == expected_result