from datetime import datetime from http import HTTPStatus import math from typing import Dict, Iterable, List import pytest from server import config from server.legacy.core.constants import ALL_VENDORS, AMAZON, APPLE, SPOTIFY from server.client.clients import DelphiClient from server.client.clients import delphi_client from server.legacy.delphi.deserializers import StreamsGroupBy, StreamsInclude from tests.legacy.delphi.fixtures.responses import ( combine_data_by_tiers, combine_vendor_data, get_streams, latest_dates_data, get_charts, ) pytest_plugins = ["tests.legacy.delphi.fixtures.responses"] def get_isrc(index: int) -> str: return f"USSM111222{index:02d}" def get_items(offset: int, limit: int = config.DEFAULT_DELPHI_PAGE_SIZE, page_count: int = 2) -> List: if offset >= limit * page_count: return [] return [{"isrc": str(i).upper()} for i in range(offset, offset + limit)] def mock_delphi_get(mocker, limit: int = config.DEFAULT_DELPHI_PAGE_SIZE, page_count: int = 2): async def return_delphi_async_value(*args, **kwargs): # last arg is params which is list of tuples, we need the last item and the second tuple entry offset_tuple = next((i for i in args[-1] if i[0] == "offset"), None) offset = offset_tuple[1] if offset_tuple else 0 items = get_items(offset, limit, page_count) return {"count": len(items), "items": items} mocked_delphi_get = mocker.patch.object(DelphiClient, "_get") mocked_delphi_get.side_effect = return_delphi_async_value return mocked_delphi_get def get_expected_args(params: dict, exclude: list = None): expected_args = [] for key, value in params.items(): if exclude and key in exclude: continue if isinstance(value, str): expected_args += [(key, v) for v in value.split(",")] else: expected_args.append((key, value)) return expected_args def base_streams_params() -> Dict: return { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "start_date": "2019-01-01", "end_date": "2019-12-01", "country_code": "us", "group_by": "date", } def base_video_params() -> Dict: return { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "start_date": "2019-01-01", "end_date": "2019-12-01", "dsp": "test_dsp", "group_by": "date,video_id", "video_id": "youtube_123", "expand_to": "related_isrcs", "content_type": "content", "limit_range": "days:800", "cursor": "field", "sort_by": "other_field", "sort_order": "asc", } def base_video_meta_params() -> Dict: return { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "artist_id": "a_1", "channel_id": "ch_2", "track_id": "tr_a", "dsp": "test_dsp", "expand_to": "related_isrcs", "sort_by": "other_field", "sort_order": "asc", } def base_charts_params() -> Dict: return { "chart_group": "youtube_trending", "country_code": "us", "dsp": "youtube", "sort_by": "field", "sort_order": "asc", } def base_video_positions_charts_params() -> Dict: return { "start_date": "2021-04-01", "end_date": "2021-04-10", "artist_id": "ar_1", "chart_group": "youtube_trending", "chart_id": "youtube_trending_us", "content_type": "content_1,content_2", "country_code": "us", "dsp": "youtube", "expand_to": "isrc", "include": "something", "is_sony": "true", "isrc": f"{get_isrc(1)},{get_isrc(2)}", "video_id": "video_123", "sort_by": "field", "sort_order": "asc", "limit_range": "date:123", "cursor": "arrow", } def base_video_positions_charts_summary_params() -> Dict: return { "artist_id": "ar_1", "chart_group": "youtube_trending", "chart_id": "youtube_trending_us", "content_type": "content_1,content_2", "country_code": "us", "dsp": "youtube", "expand_to": "isrc", "include": "something", "is_sony": "true", "isrc": f"{get_isrc(1)},{get_isrc(2)}", "video_id": "video_123", "sort_by": "field", "sort_order": "asc", } def get_video_analytics_count(params: dict, page_count: int): isrc_list = params["isrc"].split(",") start_date = datetime.fromisoformat(params["start_date"]).date() end_date = datetime.fromisoformat(params["end_date"]).date() parallel_count = min( config.DELPHI_MAX_REQUESTS_COUNT, math.ceil((end_date - start_date).days / config.DELPHI_REQUESTS_INTERVAL_SIZE) ) return (page_count // parallel_count + 1) * parallel_count * len(isrc_list) @pytest.mark.parametrize( "url,params,limit,page_count,parallel_count,get_count_func", ( ( "streams", base_streams_params(), config.DEFAULT_DELPHI_PAGE_SIZE, 3, len(ALL_VENDORS), None, ), ( "videos", base_video_meta_params(), config.DELPHI_VIDEOS_PAGE_SIZE, 3, 1, None, ), ( "video-analytics", base_video_params(), config.DELPHI_VIDEO_ANALYTICS_PAGE_SIZE, 3, 2, get_video_analytics_count, ), ( "video-analytics", base_video_params(), config.DELPHI_VIDEO_ANALYTICS_PAGE_SIZE, 4, 2, get_video_analytics_count, ), ( "video-analytics", base_video_params(), config.DELPHI_VIDEO_ANALYTICS_PAGE_SIZE, 8, 2, get_video_analytics_count, ), ( "video-analytics", base_video_params(), config.DELPHI_VIDEO_ANALYTICS_PAGE_SIZE, 9, 2, get_video_analytics_count, ), ( "charts", base_charts_params(), config.DELPHI_CHARTS_PAGE_SIZE, 3, 1, None, ), ( "video-positions/charts", base_video_positions_charts_params(), config.DELPHI_CHARTS_PAGE_SIZE, 3, 1, None, ), ( "video-positions/charts/summary", base_video_positions_charts_summary_params(), config.DELPHI_CHARTS_PAGE_SIZE, 3, 1, None, ), ), ) async def test_endpoint_full_request( url, params, limit, page_count, parallel_count, get_count_func, mocker, client, auth_header ): mocked_delphi_get = mock_delphi_get(mocker, limit=limit, page_count=page_count) response = await client.get(f"/api/delphi/{url}", params=params, headers=auth_header) assert response.status == HTTPStatus.OK resp_data = await response.json() assert mocked_delphi_get.call_count == ( get_count_func(params, page_count) if get_count_func else (page_count + 1) * parallel_count ) items = get_items(0, limit * page_count) * parallel_count assert resp_data == items @pytest.mark.parametrize( "url,params,parallel_values,parallel_key", ( ("streams", base_streams_params(), ALL_VENDORS, "dsp"), ("videos", base_video_meta_params(), None, None), ("video-analytics", base_video_params(), base_video_params()["isrc"].split(","), "isrc"), ("charts", base_charts_params(), None, None), ("video-positions/charts", base_video_positions_charts_params(), None, None), ("video-positions/charts/summary", base_video_positions_charts_summary_params(), None, None), ), ) async def test_endpoint_page_request(url, params, parallel_values, parallel_key, mocker, client, auth_header): parallel_count = len(parallel_values) if parallel_values else 1 limit = 15 params.update({"limit": limit, "offset": 30}) result = [{"data": str(i)} for i in range(limit)] async def return_delphi_async_value(*args, **kwargs): return {"count": limit, "items": result} mocked_delphi_get = mocker.patch.object(DelphiClient, "_get") mocked_delphi_get.side_effect = return_delphi_async_value response = await client.get(f"/api/delphi/{url}", params=params, headers=auth_header) assert response.status == HTTPStatus.OK resp_data = await response.json() assert resp_data == result * parallel_count assert mocked_delphi_get.call_count == parallel_count expected_args = get_expected_args(params, [parallel_key] if parallel_key else None) if parallel_values: args_mapping = { next(j[1] for j in i.args[1] if j[0] == parallel_key): i.args for i in mocked_delphi_get.call_args_list } for item in parallel_values: args = args_mapping[item] assert len(args) == 2 assert args[0] == url assert sorted(args[1]) == sorted(expected_args + [(parallel_key, item)]) else: args = mocked_delphi_get.call_args.args assert len(args) == 2 assert args[0] == url assert sorted(args[1]) == sorted(expected_args) @pytest.mark.parametrize( "params,status", ( ({}, HTTPStatus.BAD_REQUEST), ({"isrc": "USSM19902990", "start_date": "2020-10-01", "end_date": "2020-10-10"}, HTTPStatus.OK), ( { "isrc": "USSM19902990,USSM19902991", "start_date": "2020-09-30", "end_date": "2020-10-08", "group_by": StreamsGroupBy.DATE, }, HTTPStatus.OK, ), ), ) async def test_delphi_streams_view(params, status, client, auth_header, mocked_streams): response = await client.get("/api/delphi/streams", params=params, headers=auth_header) assert response.status == status if status == HTTPStatus.OK: resp_data = await response.json() params["isrc_list"] = params["isrc"].split(",") if "group_by" in params: params["group_by"] = params["group_by"].split(",") expected_data = await get_streams(**params) assert resp_data == expected_data @pytest.mark.parametrize( "params,status,expected_result", ( ({}, HTTPStatus.BAD_REQUEST, []), ( {"isrc": "USSM19902990"}, HTTPStatus.OK, [ { "country_code": "worldwide", "date": "2020-02-09", "dsp": "amazon", "isrc": "USSM19902990", "streams": 7028, }, { "country_code": "worldwide", "date": "2019-11-01", "dsp": "apple", "isrc": "USSM19902990", "streams": 79689, }, { "country_code": "worldwide", "date": "2019-11-02", "dsp": "spotify", "isrc": "USSM19902990", "streams": 300590, }, ], ), ( {"isrc": "USSM19902990,USSM19902991"}, HTTPStatus.OK, [ { "country_code": "worldwide", "date": "2020-02-08", "dsp": "amazon", "isrc": "USSM19902991", "streams": 6813, }, { "country_code": "worldwide", "date": "2019-11-03", "dsp": "apple", "isrc": "USSM19902991", "streams": 144907, }, { "country_code": "worldwide", "date": "2019-11-04", "dsp": "spotify", "isrc": "USSM19902991", "streams": 446618, }, { "country_code": "worldwide", "date": "2020-02-09", "dsp": "amazon", "isrc": "USSM19902990", "streams": 7028, }, { "country_code": "worldwide", "date": "2019-11-01", "dsp": "apple", "isrc": "USSM19902990", "streams": 79689, }, { "country_code": "worldwide", "date": "2019-11-02", "dsp": "spotify", "isrc": "USSM19902990", "streams": 300590, }, ], ), ( {"isrc": "USSM19902990,USSM19902991", "dsp": APPLE}, HTTPStatus.OK, [ { "country_code": "worldwide", "date": "2019-11-03", "dsp": "apple", "isrc": "USSM19902991", "streams": 144907, }, { "country_code": "worldwide", "date": "2019-11-01", "dsp": "apple", "isrc": "USSM19902990", "streams": 79689, }, ], ), ), ) async def test_delphi_first_stream_dates_view(params, status, expected_result, client, auth_header, mocked_first_dates): response = await client.get("/api/delphi/stream-dates", params=params, headers=auth_header) assert response.status == status if status == HTTPStatus.OK: resp_data = await response.json() assert resp_data == expected_result async def test_delphi_latest_stream_dates_view(client, auth_header, mocked_latest_dates): response = await client.get("/api/delphi/data-health/completeness", headers=auth_header) assert response.status == HTTPStatus.OK resp_data = await response.json() assert resp_data == latest_dates_data @pytest.mark.parametrize( "params,status,resp_data", ( ({}, HTTPStatus.BAD_REQUEST, {}), ( {"isrc": "USSM19902990,USSM19902991", "market": "us", "end_date": "2020-10-04"}, HTTPStatus.OK, { "first_stream_date": {"amazon": "2020-02-08", "apple": "2019-11-01", "spotify": "2019-11-02"}, "global": 26032051, "market": 8786169, }, ), ( {"isrc": "USSM19902991"}, HTTPStatus.OK, { "first_stream_date": {"amazon": "2020-02-08", "apple": "2019-11-03", "spotify": "2019-11-04"}, "global": 44028960, }, ), ), ) async def test_total_streams_count_bulk_view( params, status, resp_data, client, auth_header, mocked_streams, mocked_latest_dates, mocked_first_dates ): """Tests for total_streams_count.""" resp = await client.request("GET", "/api/delphi/total-streams-count-bulk", params=params, headers=auth_header) assert resp.status == status if status != HTTPStatus.OK: return resp_json = await resp.json() assert resp_json == resp_data @pytest.mark.parametrize( "isrc,market,include", ( ("USSM19902990", "us", (StreamsInclude.DEMOGRAPHICS,)), ("USSM19902990", "us", (StreamsInclude.ALL,)), ("USSM19902990", "us", (StreamsInclude.SAVES,)), ("USSM19902990", "us", (StreamsInclude.SKIPS,)), ("USSM19902990", "us", (StreamsInclude.SKIPS, StreamsInclude.SAVES)), ), ) async def test_apple_demographic_fields_view(isrc, market, include, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-08" data = {"items": [{"isrc": isrc, "start": start_date, "end": end_date}], "include": include, "market": market} response = await client.post("/api/delphi/apple-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK resp_data = (await response.json())[0] if StreamsInclude.DEMOGRAPHICS in include or StreamsInclude.ALL in include: assert "genders" in resp_data assert "age_bands" in resp_data if StreamsInclude.SKIPS in include or StreamsInclude.ALL in include: assert "skips" in resp_data assert "saves" not in resp_data @pytest.mark.parametrize( "isrc_list,market,resp_data", ( (["USSM19902990"], "us", [226295]), (["USSM19902990", "USSM19902991"], ["us"], [226295, 356548]), (["USSM19902990"], ["_gl", "global", "worldwide"], [412332]), (["USSM19902990"], "_gl", [412332]), (["USSM19902990"], ["global", "us"], [226295, 412332]), ), ) async def test_apple_demographic_streams_view(isrc_list, market, resp_data, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-08" isrc_items = [{"isrc": _isrc, "start": start_date, "end": end_date} for _isrc in isrc_list] data = {"items": isrc_items, "market": market} response = await client.post("/api/delphi/apple-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK resp_json = sorted(await response.json(), key=lambda x: (x["isrc"], x["market"])) assert len(resp_json) == len(resp_data) resp_json = [i["streams"] for i in resp_json] assert resp_json == resp_data @pytest.mark.parametrize( "isrc,market,include", ( ("USSM19902991", "us", (StreamsInclude.DEMOGRAPHICS,)), ("USSM19902991", "gb", (StreamsInclude.ALL,)), ("USSM19902991", "ar", (StreamsInclude.SAVES,)), ("USSM19902991", "us", (StreamsInclude.SKIPS,)), ("USSM19902991", "de", (StreamsInclude.SKIPS, StreamsInclude.SAVES)), ), ) async def test_spotify_demographic_fields_view(isrc, market, include, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-08" data = {"items": [{"isrc": isrc, "start": start_date, "end": end_date}], "include": include, "market": market} response = await client.post("/api/delphi/spotify-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK resp_data = (await response.json())[0] if StreamsInclude.DEMOGRAPHICS in include or StreamsInclude.ALL in include: assert "genders" in resp_data assert "age_bands" in resp_data if StreamsInclude.SKIPS in include or StreamsInclude.ALL in include: assert "skips" in resp_data if StreamsInclude.SAVES in include or StreamsInclude.ALL in include: assert "saves" in resp_data @pytest.mark.parametrize( "isrc_list,market,resp_data", ( (["USSM19902991"], "us", [720829]), (["USSM19902990", "USSM19902991"], ["us"], [411721, 720829]), (["USSM19902990"], "_gl", [1650455]), (["USSM19902990"], ["_gl", "global", "worldwide"], [1650455]), (["USSM19902990"], ["_gl", "us"], [1650455, 411721]), ), ) async def test_spotify_demographic_streams_view(isrc_list, market, resp_data, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-09" data = {"items": [{"isrc": isrc, "start": start_date, "end": end_date} for isrc in isrc_list], "market": market} response = await client.post("/api/delphi/spotify-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK resp_json = sorted(await response.json(), key=lambda x: x["isrc"]) assert len(resp_json) == len(resp_data) resp_json = [i["streams"] for i in resp_json] assert resp_json == resp_data async def get_combined_data( vendor: str, start_date: str, end_date: str, isrc_list: List[str], market: str or None, include: Iterable[str], wrap_none: bool = True, ) -> List[dict]: expected_data = await get_streams( start_date=start_date, end_date=end_date, isrc_list=isrc_list, dsp_list=[vendor], country_code_list=[market] if market else None, include=include, ) combined_data = combine_vendor_data(expected_data, isrc_list, wrap_none=wrap_none) combined_data.sort(key=lambda x: (x["dsp"], x["country_code"])) for item in combined_data: item["market"] = item["country_code"] if StreamsInclude.DEMOGRAPHICS in include or StreamsInclude.ALL in include: item["age_bands"] = item[f"{vendor}_age_bands"] del item[f"{vendor}_age_bands"] if StreamsInclude.SKIPS in include or StreamsInclude.ALL in include: item["skips"] = item[f"{vendor}_streams_info"]["skips"] if vendor == SPOTIFY and (StreamsInclude.SAVES in include or StreamsInclude.ALL in include): item["saves"] = item[f"{vendor}_streams_info"]["saves"] del item["country_code"] del item[f"{vendor}_streams_info"] return combined_data @pytest.mark.parametrize( "isrc_list,market,include", ( (["USSM19902990"], None, (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.ALL,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.ALL,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.SKIPS,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.SKIPS,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.SAVES,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.SAVES, StreamsInclude.SKIPS)), ), ) async def test_apple_demographic_combined_view(isrc_list, market, include, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-08" isrc_items = [{"isrc": _isrc, "start": start_date, "end": end_date} for _isrc in isrc_list] data = {"items": isrc_items, "combine_isrc": "true", "include": include} if market: data["market"] = market response = await client.post("/api/delphi/apple-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK combined_data = await get_combined_data(APPLE, start_date, end_date, isrc_list, market, include) resp_data = await response.json() resp_data.sort(key=lambda x: (x["dsp"], x["market"])) assert resp_data == combined_data @pytest.mark.parametrize( "isrc_list,market,include", ( (["USSM19902991"], None, (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.DEMOGRAPHICS,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.ALL,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.ALL,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.SAVES,)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.SKIPS,)), (["USSM19902990", "USSM19902991"], None, (StreamsInclude.SAVES, StreamsInclude.SKIPS)), (["USSM19902990", "USSM19902991"], "us", (StreamsInclude.SKIPS, StreamsInclude.SAVES)), ), ) async def test_spotify_demographic_combined_view(isrc_list, market, include, client, auth_header, mocked_streams): start_date, end_date = "2020-10-01", "2020-10-09" data = { "items": [{"isrc": isrc, "start": start_date, "end": end_date} for isrc in isrc_list], "combine_isrc": "true", "include": include, } if market: data["market"] = market response = await client.post("/api/delphi/spotify-demographics", json=data, headers=auth_header) assert response.status == HTTPStatus.OK combined_data = await get_combined_data(SPOTIFY, start_date, end_date, isrc_list, market, include) resp_data = await response.json() resp_data.sort(key=lambda x: (x["dsp"], x["market"])) assert resp_data == combined_data async def get_amazon_streams( start_date: str, end_date: str, isrc_list: List[str], combine_isrc: bool = False, combine_tiers: bool = False ) -> List[dict]: group_by = [StreamsGroupBy.DATE] if combine_tiers: group_by.append(StreamsGroupBy.SUB_DSP) data = await get_streams( start_date=start_date, end_date=end_date, isrc_list=isrc_list, dsp_list=[AMAZON], include=StreamsInclude.ALL, group_by=group_by, ) if combine_isrc: data = combine_vendor_data(data, isrc_list, wrap_none=False) if combine_tiers: data = combine_data_by_tiers(data, wrap_none=False) for item in data: item.update(item["amazon_streams_info"]) del item["amazon_streams_info"] del item["dsp"] return data async def test_amazon_tracks_streams_v1_combined_request(client, auth_header, mocked_streams): def flat_params(_params): _flat = [] for k, v in _params.items(): if isinstance(v, list): _flat.extend([(k, i) for i in v]) else: _flat.append((k, v)) return _flat isrc_list = ["USSM19902990", "USSM19902991"] start_date = "2020-10-01" end_date = "2020-10-09" params = {"isrc": ",".join(isrc_list), "start_date": start_date, "end_date": end_date, "group_by": "date"} response = await client.get("/api/delphi/amazon/tracks/v1/streams", params=params, headers=auth_header) assert response.status == 200 resp_data = await response.json() expected_data = await get_amazon_streams(start_date, end_date, isrc_list) assert resp_data == {"count": len(expected_data), "items": expected_data} params["combine_isrc"] = "true" response = await client.get("/api/delphi/amazon/tracks/v1/streams", params=params, headers=auth_header) assert response.status == 200 resp_data = await response.json() expected_data = await get_amazon_streams(start_date, end_date, isrc_list, combine_isrc=True) for item in resp_data["items"]: item["isrc"] = ",".join(sorted(item["isrc"].split(","))) assert resp_data == {"count": len(expected_data), "items": expected_data} params["group_by"] = ["date", "sub_dsp"] params["combine_isrc"] = "false" params["combine_tiers"] = "true" response = await client.get("/api/delphi/amazon/tracks/v1/streams", params=flat_params(params), headers=auth_header) assert response.status == 200 resp_data = await response.json() expected_data = await get_amazon_streams(start_date, end_date, isrc_list, combine_tiers=True) assert resp_data == {"count": len(expected_data), "items": expected_data} params["combine_isrc"] = "true" params["combine_tiers"] = "true" response = await client.get("/api/delphi/amazon/tracks/v1/streams", params=flat_params(params), headers=auth_header) assert response.status == 200 resp_data = await response.json() expected_data = await get_amazon_streams(start_date, end_date, isrc_list, combine_isrc=True, combine_tiers=True) assert resp_data == {"count": len(expected_data), "items": expected_data} async def test_get_video_request(mocker, client, auth_header): """Tests get single video metadata.""" video_id = "vid_150" result = {"data": "test", "dsp_video_id": "150"} async def return_delphi_async_value(*args, **kwargs): return result mocked_delphi_get = mocker.patch.object(DelphiClient, "_get") mocked_delphi_get.side_effect = return_delphi_async_value response = await client.get(f"/api/delphi/videos/{video_id}", headers=auth_header) assert response.status == 200 resp_data = await response.json() result["url"] = "https://www.youtube.com/watch?v=150" assert resp_data == result assert await mocked_delphi_get.called_once_with(f"videos/{video_id}") @pytest.mark.parametrize("views_count, expected_count", ((10000, 3), (83, 5), (84, 4))) async def test_videos_views_filter(views_count, expected_count, client, auth_header, mocked_videos): data = {"isrc": "abc", "views": views_count} response = await client.get("/api/delphi/videos", params=data, headers=auth_header) assert response.status == 200 resp_data = await response.json() assert not [i for i in resp_data if i["views"] < views_count] assert len(resp_data) == expected_count @pytest.mark.parametrize( "params,status", ( ({}, HTTPStatus.OK), ({"dsp": "spotify,apple"}, HTTPStatus.OK), ({"dsp": "apple"}, HTTPStatus.OK), ({"dsp": "spotify"}, HTTPStatus.OK), ({"dsp": "apple", "sort_by": "rank", "sort_order": "asc"}, HTTPStatus.OK), ({"dsp": "spotify", "sort_by": "rank", "sort_order": "desc"}, HTTPStatus.OK), ({"dsp": "spotify,apple", "sort_by": "rank", "sort_order": "asc"}, HTTPStatus.OK), ({"sort_by": "rank", "sort_order": "asc"}, HTTPStatus.OK), ), ) async def test_delphi_charts_view(params, status, client, auth_header, mocked_charts): response = await client.get("/api/delphi/charts", params=params, headers=auth_header) assert response.status == status dsp = params.get("dsp") keys = ["chart_group", "chart_id", "country_code", "dsp", "name", "num_tracks", "rank", "uri"] if response.status == HTTPStatus.OK and (dsp == "spotify,apple" or not dsp): resp_data = await response.json() expected_data = await get_charts(**params) assert all(a in resp_data for a in ["items", "count"]) assert all(a in list(b.keys()) for a in keys for b in resp_data["items"]) assert expected_data["count"] == resp_data["count"] @pytest.mark.parametrize( "params,status", ( ({"dsp": "apple"}, HTTPStatus.OK), ({"dsp": "spotify"}, HTTPStatus.OK), ({"dsp": "apple", "sort_by": "rank", "sort_order": "asc"}, HTTPStatus.OK), ({"dsp": "spotify", "sort_by": "rank", "sort_order": "desc"}, HTTPStatus.OK), ({"sort_by": "rank", "sort_order": "asc"}, HTTPStatus.OK), ({"dsp": "spotify,apple"}, HTTPStatus.OK), ), ) async def test_delphi_one_dsp_charts_view(mocker, params, status, client, auth_header): async def return_delphi_async_value(*args, **kwargs): return await get_charts(**params) mocked_get_charts = mocker.patch.object(delphi_client.DelphiClient, "get_charts") mocked_get_charts.side_effect = return_delphi_async_value response = await client.get("/api/delphi/charts", params=params, headers=auth_header) assert response.status == status dsp = params.get("dsp") sort_by = params.get("sort_by") sort_order = params.get("sort_order") keys = ["chart_group", "chart_id", "country_code", "dsp", "name", "num_tracks", "rank", "uri"] if status == HTTPStatus.OK: expected_data = await get_charts(**params) resp_data = await response.json() assert all(a in resp_data for a in ["items", "count"]) assert all(a in list(b.keys()) for a in keys for b in resp_data["items"]) assert expected_data["count"] == resp_data["count"] if dsp != "spotify" and not (sort_by and sort_order): assert resp_data == expected_data def get_cpd_result(offset: int = 0, limit: int = 5) -> List[dict]: return [{"dsp": f"dsp_{i}", "chart_group": f"cg_{i}"} for i in range(offset, limit)] @pytest.mark.parametrize( "params,expected_result", ( ({}, get_cpd_result()), ({"dsp": "dsp_1", "chart_group": "cg_2"}, []), ({"dsp": "dsp_1,dsp_2", "chart_group": "cg_2"}, get_cpd_result(2, 3)), ({"dsp": "dsp_1,dsp_2,dsp_3", "chart_group": "cg_2,cg_3"}, get_cpd_result(2, 4)), ), ) async def test_delphi_chart_positions_dates_view(params, expected_result, mocker, client, auth_header): """Tests the delphi_chart_positions_dates_view endpoint.""" async def return_delphi_async_value(*args, **kwargs): return {"count": 999, "items": get_cpd_result()} mocked_delphi_get = mocker.patch.object(DelphiClient, "_get") mocked_delphi_get.side_effect = return_delphi_async_value response = await client.get("/api/delphi/data-health/chart-positions", params=params, headers=auth_header) assert response.status == HTTPStatus.OK resp_data = await response.json() assert resp_data == expected_result @pytest.mark.parametrize( "params,status", ( ({}, HTTPStatus.BAD_REQUEST), ({"isrc": "AAA111222333"}, HTTPStatus.BAD_REQUEST), ({"start_date": "2021-05-01", "end_date": "2021-05-10"}, HTTPStatus.BAD_REQUEST), ({"isrc": "AAA111222333", "start_date": "2021-05-01", "end_date": "2021-05-10"}, HTTPStatus.OK), ), ) async def test_delphi_tiktok_tracks_analytics_view(params, status, mocker, client, auth_header, mocked_tracks): result = {"min_date": None, "max_date": None, "breakdowns": {"daily": {}}} async def return_delphi_async_value(*args, **kwargs): return result mocked_delphi_get = mocker.patch.object(DelphiClient, "_get") mocked_delphi_get.side_effect = return_delphi_async_value response = await client.get("/api/delphi/tiktok/tracks/analytics", headers=auth_header, params=params) assert response.status == status if status == HTTPStatus.OK: resp_data = await response.json() assert resp_data == result assert mocked_delphi_get.call_count == 1 assert await mocked_delphi_get.called_once_with( ( "tiktok/tracks/analytics", list( sorted([(k, v) for k, v in params.items()], key=lambda x: x[0]), ), ), {}, ) @pytest.mark.parametrize( "params,expected_result", ( ( {"isrc": get_isrc(10), "start_date": "2020-09-04", "end_date": "2020-09-06"}, { "breakdowns": { "daily": { "comments": [3036, 1916, 2097], "creations": [386, 389, 369], "favorites": [1052, 496, 673], "likes": [136242, 83270, 58589], "shares": [7055, 3489, 2899], "video_views": [1166934, 906479, 725255], }, }, "days_count": 3, "max_date": "2020-09-06", "min_date": "2020-09-04", }, ), ( { "isrc": get_isrc(5), "start_date": "2020-09-03", "end_date": "2020-09-04", "metrics": "likes,video_views", "breakdowns": "daily,totals,country_totals", }, { "breakdowns": { "country_totals": { "ca": {"likes": 3649, "video_views": 48967}, "gb": {"likes": 5436, "video_views": 59193}, "us": {"likes": 89154, "video_views": 779221}, "worldwide": {"likes": 186847, "video_views": 1990703}, }, "daily": {"likes": [132104, 68121], "video_views": [888270, 583467]}, "totals": {"likes": 186847, "video_views": 1990703}, }, "days_count": 2, "max_date": "2020-09-04", "min_date": "2020-09-03", }, ), ( { "isrc": get_isrc(8), "start_date": "2020-09-06", "end_date": "2020-09-07", "metrics": "comments,shares", "country_code": "worldwide,us", "content_type": "pgc", "breakdowns": "content_type_country", }, { "breakdowns": { "content_type_country": { "pgc": { "us": {"comments": [208, 230], "shares": [282, 181]}, "worldwide": {"comments": [804, 914], "shares": [1121, 1040]}, }, }, }, "days_count": 2, "max_date": "2020-09-07", "min_date": "2020-09-06", }, ), ( { "isrc": get_isrc(21), "start_date": "2020-09-07", "end_date": "2020-09-09", "metrics": "comments,favorites", "breakdowns": "totals,daily", }, { "breakdowns": { "daily": {"comments": [4102, 8242, 5124], "favorites": [3827, 4490, 2564]}, "totals": {"comments": 7350, "favorites": 1966}, }, "days_count": 3, "max_date": "2020-09-09", "min_date": "2020-09-07", }, ), ( { "isrc": get_isrc(3), "start_date": "2020-10-14", "end_date": "2020-10-16", "country_code": "us,ca", "breakdowns": "totals,daily", }, { "breakdowns": { "daily": { "comments": [5103, None, 2104], "creations": [545, None, 514], "favorites": [1552, None, 556], "likes": [282280, None, 42309], "shares": [23799, None, 2653], "video_views": [1914543, None, 578545], }, "totals": { "ca": { "comments": 174, "creations": 24, "favorites": 57, "likes": 5474, "shares": 388, "video_views": 73450, }, "us": { "comments": 2952, "creations": 448, "favorites": 1153, "likes": 133730, "shares": 11747, "video_views": 1168831, }, }, }, "days_count": 3, "max_date": "2020-10-16", "min_date": "2020-10-14", }, ), ( { "isrc": get_isrc(7), "start_date": "2020-09-07", "end_date": "2020-09-08", "metrics": "comments,favorites", "breakdowns": "totals,daily,content_type_country,country_totals", }, { "breakdowns": { "country_totals": { "ca": {"comments": 116, "favorites": 38}, "gb": {"comments": 239, "favorites": 54}, "us": {"comments": 1968, "favorites": 769}, "worldwide": {"comments": 4900, "favorites": 1311}, }, "daily": {"comments": [2051, 4121], "favorites": [1913, 2245]}, "totals": {"comments": 4900, "favorites": 1311}, "content_type_country": { "pgc": {"worldwide": {"comments": [914, 885], "favorites": [169, 136]}}, "ugc": {"worldwide": {"comments": [1652, 3776], "favorites": [1907, 2214]}}, }, }, "days_count": 2, "max_date": "2020-09-08", "min_date": "2020-09-07", }, ), ( { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "start_date": "2020-09-07", "end_date": "2020-09-08", "metrics": "comments,favorites", "breakdowns": "totals,daily,content_type_country,country_totals", }, { "breakdowns": { "country_totals": { "ca": {"comments": 232, "favorites": 76}, "gb": {"comments": 478, "favorites": 108}, "us": {"comments": 3936, "favorites": 1538}, "worldwide": {"comments": 9800, "favorites": 2622}, }, "daily": {"comments": [4102, 8242], "favorites": [3826, 4490]}, "totals": {"comments": 9800, "favorites": 2622}, "content_type_country": { "pgc": {"worldwide": {"comments": [1828, 1770], "favorites": [338, 272]}}, "ugc": {"worldwide": {"comments": [3304, 7552], "favorites": [3814, 4428]}}, }, }, "days_count": 2, "max_date": "2020-09-08", "min_date": "2020-09-07", }, ), ( { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "start_date": "2020-09-07", "end_date": "2020-09-08", "metrics": "comments,favorites", "breakdowns": "totals,daily,content_type_country,country_totals", "country_code": "us,gb", }, { "breakdowns": { "daily": {"comments": [8204, 16484], "favorites": [7654, 8980]}, "totals": {"gb": {"comments": 478, "favorites": 108}, "us": {"comments": 3936, "favorites": 1538}}, "country_totals": { "gb": {"comments": 478, "favorites": 108}, "us": {"comments": 3936, "favorites": 1538}, }, "content_type_country": { "pgc": { "gb": {"comments": [72, 84], "favorites": [10, 10]}, "us": {"comments": [460, 354], "favorites": [106, 60]}, }, "ugc": { "gb": {"comments": [80, 160], "favorites": [94, 62]}, "us": {"comments": [2308, 6412], "favorites": [3160, 4076]}, }, }, }, "days_count": 2, "max_date": "2020-09-08", "min_date": "2020-09-07", }, ), ( { "isrc": f"{get_isrc(1)},{get_isrc(2)}", "start_date": "2020-09-07", "end_date": "2020-09-08", "metrics": "comments,favorites", "breakdowns": "country_totals", "country_code": "us,gb", }, { "breakdowns": { "country_totals": { "gb": {"comments": 478, "favorites": 108}, "us": {"comments": 3936, "favorites": 1538}, }, }, "days_count": 2, "max_date": "2020-09-08", "min_date": "2020-09-07", }, ), ( { "isrc": get_isrc(2), "start_date": "2020-09-07", "end_date": "2020-09-08", "metrics": "comments,favorites", "breakdowns": "country_totals", "country_code": "us,gb", }, { "breakdowns": { "country_totals": { "gb": {"comments": 478, "favorites": 108}, "us": {"comments": 3936, "favorites": 1538}, }, }, "days_count": 2, "max_date": "2020-09-08", "min_date": "2020-09-07", }, ), ), ) async def test_delphi_tiktok_tracks_analytics_custom_view( params, expected_result, client, auth_header, mocked_tiktok_tracks_analytics, mocked_regions, mocked_tracks ): response = await client.get("/api/delphi/tiktok/tracks/analytics", headers=auth_header, params=params) assert response.status == HTTPStatus.OK resp_data = await response.json() assert resp_data == expected_result @pytest.mark.parametrize( "params,status,expected_result", ( ({}, HTTPStatus.BAD_REQUEST, None), ({"chart_id": "c_id"}, HTTPStatus.BAD_REQUEST, None), ({"chart_id": "c_id", "start_date": "2020-08-08", "end_date": "2020-08-10"}, HTTPStatus.BAD_REQUEST, None), ( {"chart_id": "c_id", "start_date": "2020-08-08", "end_date": "2020-08-10", "dsp": "spotify"}, HTTPStatus.BAD_REQUEST, None, ), ( { "chart_id": "c_id", "start_date": "2020-08-08", "end_date": "2020-08-10", "dsp": "spotify", "isrc": "USUM72014730", }, HTTPStatus.BAD_REQUEST, {}, ), ( { "chart_id": "c_id", "start_date": "2020-08-08", "end_date": "2020-08-10", "dsp": "spotify", "isrc": "USUM72014730", "metrics": "positions", }, HTTPStatus.OK, [ { "isrc": "USUM72014730", "max_date": "2020-08-10", "max_position": 119, "metrics": {"positions": [119, 92, 81]}, "min_date": "2020-08-08", "min_position": 81, "track_id": "70rFIfYN7lU6iTgUIVP42w", }, ], ), ( { "chart_id": "c_id", "start_date": "2020-09-22", "end_date": "2020-09-27", "dsp": "apple", "isrc": "USUM72014730,USRC11600642", "metrics": "positions", }, HTTPStatus.OK, [ { "isrc": "USRC11600642", "max_date": "2020-09-24", "max_position": 194, "metrics": {"positions": [None, None, 194]}, "min_date": "2020-09-22", "min_position": 194, "track_id": "6YZdkObH88npeKrrkb8Ggf", }, { "isrc": "USUM72014730", "max_date": "2020-09-24", "max_position": 67, "metrics": {"positions": [67, 67, 65]}, "min_date": "2020-09-22", "min_position": 65, "track_id": "70rFIfYN7lU6iTgUIVP42w", }, { "isrc": "USUM72014730", "max_date": "2020-09-27", "max_position": 42, "metrics": {"positions": [36, 41, 42]}, "min_date": "2020-09-25", "min_position": 36, "track_id": "7kDUspsoYfLkWnZR7qwHZl", }, ], ), ( { "chart_id": "c_id", "start_date": "2020-09-22", "end_date": "2020-09-27", "dsp": "apple", "isrc": "USUM72014730,USRC11600642", "metrics": "positions", "group_by": "isrc", }, HTTPStatus.OK, [ { "isrc": "USRC11600642", "max_date": "2020-09-24", "max_position": 194, "metrics": {"positions": [None, None, 194]}, "min_date": "2020-09-22", "min_position": 194, "track_id": "6YZdkObH88npeKrrkb8Ggf", }, { "isrc": "USUM72014730", "max_date": "2020-09-27", "max_position": 67, "metrics": {"positions": [67, 67, 65, 36, 41, 42]}, "min_date": "2020-09-22", "min_position": 36, "track_id": None, }, ], ), ), ) async def test_delphi_chart_analytics_view(params, status, expected_result, client, auth_header, mocked_get): response = await client.get("/api/delphi/charts/analytics", headers=auth_header, params=params) assert response.status == status if status == HTTPStatus.OK: resp_data = await response.json() assert sorted(resp_data, key=lambda i: (i["isrc"], i["track_id"])) == expected_result @pytest.mark.parametrize( "params,status,expected_result", ( ({}, HTTPStatus.BAD_REQUEST, None), ({"dsp": "spotify"}, HTTPStatus.BAD_REQUEST, None), ( {"isrc": "USSM19902990", "dsp": "spotify", "chart_breakdown": "daily", "chart_type": "regional,viral"}, HTTPStatus.OK, [ { "chart_meta": { "breakdown": "daily", "chart_id": "regional_daily_ca", "country_code": "ca", "name": "Spotify Top Daily Regional CA", "rank": 7, "total_positions": 200, "type": "regional", }, "licensors": ["sme"], "lifetime_metrics": { "earliest_position": 198, "earliest_position_date": "2017-12-31", "latest_position": 188, "latest_position_date": "2021-10-31", "max_position": 198, "max_position_date": "2017-12-31", "min_position": 112, "min_position_date": "2018-01-01", "total_days": 3, }, "public_meta": { "artists": [{"artist_id": "3fMbdgg4jU18AjLCKBhRSm", "name": "Michael Jackson"}], "image_url": "https://i.scdn.co/image/ab67616d0000b2734121faee8df82c526cbab2be", "isrc": "USSM19902990", "name": "Beat It", "release_date": "1982-11-30", "track_id": "1OOtq8tRnDM8kG2gqUPjAj", "uri": "https://open.spotify.com/track/1OOtq8tRnDM8kG2gqUPjAj", }, }, { "chart_meta": { "breakdown": "daily", "chart_id": "viral_daily_ie", "country_code": "ie", "name": "Spotify Top Daily Viral IE", "rank": 30, "total_positions": 200, "type": "viral", }, "licensors": ["sme"], "lifetime_metrics": { "earliest_position": 169, "earliest_position_date": "2017-07-20", "latest_position": 184, "latest_position_date": "2021-10-31", "max_position": 196, "max_position_date": "2017-10-31", "min_position": 166, "min_position_date": "2017-09-14", "total_days": 5, }, "public_meta": { "artists": [{"artist_id": "3fMbdgg4jU18AjLCKBhRSm", "name": "Michael Jackson"}], "image_url": "https://i.scdn.co/image/ab67616d0000b2734121faee8df82c526cbab2be", "isrc": "USSM19902990", "name": "Beat It", "release_date": "1982-11-30", "track_id": "1OOtq8tRnDM8kG2gqUPjAj", "uri": "https://open.spotify.com/track/1OOtq8tRnDM8kG2gqUPjAj", }, }, ], ), ( { "isrc": "USSM19902990", "dsp": "spotify", "chart_breakdown": "daily,weekly", "chart_type": "regional", }, HTTPStatus.OK, [ { "chart_meta": { "breakdown": "daily", "chart_id": "regional_daily_ca", "country_code": "ca", "name": "Spotify Top Daily Regional CA", "rank": 7, "total_positions": 200, "type": "regional", }, "licensors": ["sme"], "lifetime_metrics": { "earliest_position": 198, "earliest_position_date": "2017-12-31", "latest_position": 188, "latest_position_date": "2021-10-31", "max_position": 198, "max_position_date": "2017-12-31", "min_position": 112, "min_position_date": "2018-01-01", "total_days": 3, }, "public_meta": { "artists": [{"artist_id": "3fMbdgg4jU18AjLCKBhRSm", "name": "Michael Jackson"}], "image_url": "https://i.scdn.co/image/ab67616d0000b2734121faee8df82c526cbab2be", "isrc": "USSM19902990", "name": "Beat It", "release_date": "1982-11-30", "track_id": "1OOtq8tRnDM8kG2gqUPjAj", "uri": "https://open.spotify.com/track/1OOtq8tRnDM8kG2gqUPjAj", }, }, { "chart_meta": { "breakdown": "weekly", "chart_id": "regional_weekly_gb", "country_code": "gb", "name": "Spotify Top Weekly Regional GB", "rank": 5, "total_positions": 200, "type": "regional", }, "licensors": ["sme"], "lifetime_metrics": { "earliest_position": 154, "earliest_position_date": "2017-07-20", "latest_position": 186, "latest_position_date": "2021-10-31", "max_position": 197, "max_position_date": "2021-10-30", "min_position": 138, "min_position_date": "2017-09-14", "total_days": 6, }, "public_meta": { "artists": [{"artist_id": "3fMbdgg4jU18AjLCKBhRSm", "name": "Michael Jackson"}], "image_url": "https://i.scdn.co/image/ab67616d0000b2734121faee8df82c526cbab2be", "isrc": "USSM19902990", "name": "Beat It", "release_date": "1982-11-30", "track_id": "1OOtq8tRnDM8kG2gqUPjAj", "uri": "https://open.spotify.com/track/1OOtq8tRnDM8kG2gqUPjAj", }, }, ], ), ), ) async def test_delphi_tracks_charts_view(params, status, expected_result, client, auth_header, mocked_get): response = await client.get("/api/delphi/tracks/charts", headers=auth_header, params=params) assert response.status == status if status == HTTPStatus.OK: resp_data = await response.json() resp_data = sorted(resp_data, key=lambda i: (i["public_meta"]["isrc"], i["chart_meta"]["chart_id"])) assert resp_data == expected_result