from datetime import date, timedelta from typing import List, Optional from unittest.mock import AsyncMock from http import HTTPStatus import pytest from server.client.clients.delphi_client import DelphiClient from server.constants.charts import ChartBreakdown from server.constants.dsp import DSP_URL_MAPPING from server.utils.charts import get_chart_id from server.utils.delphi.converters import str_to_date from server.utils.delphi.misc import nullable_min, nullable_max def get_position(tr_index: int, pos_index: int, is_weekly: bool, min_date: date) -> Optional[int]: return ( abs((tr_index * 5 + pos_index * (-1 if pos_index % 3 else 1)) % 100) if pos_index % 5 and (not is_weekly or (min_date + timedelta(days=pos_index)).weekday() == 3) else None ) def get_min_position(tr_index_list: List[int], pos_index: int, is_weekly: bool, min_date: date) -> Optional[int]: return nullable_min(*[get_position(i, pos_index, is_weekly, min_date) for i in tr_index_list]) def get_track_positions( min_date: date = date(2022, 5, 8), max_date: date = date(2022, 6, 19), track_count: int = 3, is_weekly: bool = False, is_result: bool = False, start_date: Optional[date] = None, end_date: Optional[date] = None, with_null: bool = True, ) -> dict: if not start_date and not end_date: start_date, end_date = min_date, max_date position_count = (max_date - min_date).days + 1 tr_index_list = list(range(1, 4)) position_list = ( [None] * (min_date - start_date).days + [get_min_position(tr_index_list, j, is_weekly, min_date) for j in range(position_count)] + [None] * (end_date - max_date).days ) if is_result: return { "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "in_chart": len([i for i in position_list if i is not None]), "peak_position": nullable_min(*position_list), "min_position": nullable_max(*position_list), "items": [ {"date": (start_date + timedelta(days=i)).isoformat(), "position": position} for i, position in enumerate(position_list) if ( (with_null or position is not None) and (not is_weekly or (start_date + timedelta(days=i)).weekday() == 3) ) ], } else: item_list = [] for i in range(1, track_count + 1): position_list = [get_position(i, j, is_weekly, min_date) for j in range(position_count)] item_list.append( { "track_id": f"tr_id_{i}", "isrcs": [f"AABBCCDD{i:02d}{j:02d}" for j in range(1, min(4, i))], "min_date": min_date.isoformat(), "max_date": max_date.isoformat(), "min_position": nullable_min(*position_list), "max_position": nullable_max(*position_list), "metrics": {"positions": position_list}, } ) return {"count": track_count, "items": item_list} @pytest.mark.parametrize( "params,status_code", ( ({}, HTTPStatus.BAD_REQUEST), ({"isrc": "AABBCCDDEE01"}, HTTPStatus.BAD_REQUEST), ({"country_code": "us"}, HTTPStatus.BAD_REQUEST), ({"dsp": "spotify"}, HTTPStatus.BAD_REQUEST), ({"isrc": "AABBCCDDEE01", "dsp": "spotify", "country_code": "us"}, HTTPStatus.OK), ({"isrc": "AABBCCDDEE01", "dsp": "spotify", "country_code": "us", "type": "weekly"}, HTTPStatus.OK), ( { "isrc": "AABBCCDDEE01", "dsp": "spotify", "country_code": "us", "type": "weekly", "start_date": "2022-05-05", "end_date": "2022-06-21", "force_dates": "true", }, HTTPStatus.OK, ), ( { "isrc": "AABBCCDDEE01", "dsp": "spotify", "country_code": "us", "type": "daily", "start_date": "2022-05-05", "end_date": "2022-06-21", "force_dates": "true", }, HTTPStatus.OK, ), ( { "isrc": "AABBCCDDEE01", "dsp": "spotify", "country_code": "us", "type": "daily", "start_date": "2022-05-05", "end_date": "2022-06-21", "force_dates": "true", "with_null": "false", }, HTTPStatus.OK, ), ), ) async def test_get_charts_tracks_positions(params: dict, status_code: HTTPStatus, mocker, client, auth_header): isrc, dsp, country_code, start_date, end_date, chart_breakdown, with_null, force_dates = ( params.get("isrc"), params.get("dsp"), params.get("country_code"), str_to_date(params.get("start_date")), str_to_date(params.get("end_date")), params.get("type"), params.get("with_null", "true") == "true", params.get("force_dates", "false") == "true", ) is_weekly = chart_breakdown == ChartBreakdown.WEEKLY.value async_delphi_mock = AsyncMock(return_value=get_track_positions(is_weekly=is_weekly)) mocked_delphi_get = mocker.patch.object(DelphiClient, "_get", side_effect=async_delphi_mock) response = await client.get("/api/charts/tracks/positions/", params=params, headers=auth_header) assert response.status == status_code if status_code != HTTPStatus.OK: return assert mocked_delphi_get.call_count == 1 assert len(mocked_delphi_get.call_args.args) == 2 assert mocked_delphi_get.call_args.args[0] == f"{DSP_URL_MAPPING[dsp]}/charts/analytics" assert sorted(mocked_delphi_get.call_args.args[1], key=lambda i: i[0]) == [ ( "chart_id", get_chart_id(dsp=dsp, chart_breakdown=chart_breakdown or ChartBreakdown.DAILY, country_code=country_code), ), ("end_date", (end_date or (date.today() + timedelta(weeks=1))).isoformat()), ("isrc", isrc), ("metrics", "positions"), ("start_date", (start_date or date(1900, 1, 1)).isoformat()), ] response = await response.json() assert response == get_track_positions( is_result=True, track_count=1, is_weekly=is_weekly, with_null=with_null, **({"start_date": start_date, "end_date": end_date} if force_dates else {}), )