from datetime import date from inspect import getfullargspec, signature from typing import Dict, List from apollo_utils.service.exceptions import APIError from http import HTTPStatus import pytest from server.legacy.core import utils as legacy_utils from server.legacy.core.constants import ( AMAZON, APPLE, DELPHI_GLOBAL_MARKET, GLOBAL_MARKET, GLOBAL_MARKET_CODE, SPOTIFY, SPOTIFY_URI_PREFIX, ) from server.utils.args.getter_setter import get_arg, set_arg from server.utils.client.base.request.retry import retry from server.utils.client.delphi.request_per_item import request_per_item, request_per_item_many from server.utils.pagination.delphi_pagination import delphi_pagination @pytest.mark.parametrize("result_type", ("dict", "list")) async def test_request_in_chunks(result_type, loop): """Tests for request_in_chunks.""" def gen_data(obj_id: int) -> Dict: """Generate sample data.""" return {"data": f"{result_type}{str(obj_id)}", "id": obj_id} async def func(ids: str or List[str], res_type: str) -> List or Dict: """Function to test decorator.""" if res_type == "list": return [gen_data(i) for i in ids] if res_type == "dict": return {i: gen_data(i) for i in ids} rt_mapping = {"dict": legacy_utils.DictResult, "list": legacy_utils.ListResult} decorated_func = legacy_utils.request_in_chunks(2, items_index=0, result_type=rt_mapping[result_type])(func) id_list = list(range(5)) result = await decorated_func(id_list, result_type) assert result == await func(id_list, result_type) class FakeAPIError(APIError): pass @pytest.mark.parametrize( "count, wait_rate, max_timeout, func_result, expected_count, expected_result, auth_count", ( (2, 0, 1, FakeAPIError(original_status_code=HTTPStatus.BAD_GATEWAY), 3, FakeAPIError, 0), (2, 0, 1, [FakeAPIError(original_status_code=HTTPStatus.BAD_GATEWAY), "r0"], 2, "r0", 0), ( 2, 0, 2, [FakeAPIError(original_status_code=HTTPStatus.TOO_MANY_REQUESTS, headers={"Retry-After": 1}), "r1"], 2, "r1", 0, ), ( 2, 0, 2, FakeAPIError(original_status_code=HTTPStatus.TOO_MANY_REQUESTS, headers={"Retry-After": 1}), 3, FakeAPIError, 0, ), ( 2, 0, 0, FakeAPIError(original_status_code=HTTPStatus.TOO_MANY_REQUESTS, headers={"Retry-After": 1}), 1, FakeAPIError, 0, ), ( 2, 0, 1, FakeAPIError(original_status_code=HTTPStatus.TOO_MANY_REQUESTS, headers={"Retry-After": 1}), 2, FakeAPIError, 0, ), (2, 0, 2, FakeAPIError(original_status_code=HTTPStatus.UNAUTHORIZED), 2, FakeAPIError, 1), (2, 0, 2, [FakeAPIError(original_status_code=HTTPStatus.UNAUTHORIZED), "r2"], 2, "r2", 1), (2, 0, 2, [FakeAPIError(original_status_code=HTTPStatus.METHOD_NOT_ALLOWED), "r3"], 1, FakeAPIError, 0), (2, 0, 2, [FakeAPIError(original_status_code=HTTPStatus.INTERNAL_SERVER_ERROR), "r4"], 2, "r4", 0), (2, 1, 1, FakeAPIError(original_status_code=HTTPStatus.INTERNAL_SERVER_ERROR), 2, FakeAPIError, 0), (2, 0, 1, FakeAPIError(original_status_code=HTTPStatus.INTERNAL_SERVER_ERROR), 3, FakeAPIError, 0), (2, 0, 1, Exception(), 1, Exception, 0), ), ) async def test_retry( count, wait_rate, max_timeout, func_result, expected_count, expected_result, auth_count, loop, mocker ): """Tests for retry decorator.""" class AsyncMock(mocker.Mock): async def __call__(self, *args, **kwargs): return super(AsyncMock, self).__call__(*args, **kwargs) func = AsyncMock() func.side_effect = func_result auth_func = AsyncMock() decorated_func = retry(count, wait_rate, max_timeout, auth_func if auth_count else None)(func) try: result = await decorated_func(None) assert result == expected_result except Exception as ex: assert isinstance(ex, expected_result) assert func.call_count == expected_count assert auth_func.call_count == auth_count @pytest.mark.parametrize( "args, expected_result", (((1, 2), 1), ((1.1, 1.8), 1.1), ((None, 2), 2), ((3, None), 3), ((None, None), None)) ) async def test_get_optional_min(args, expected_result, loop): """Tests for get_optional_min.""" assert legacy_utils.get_optional_min(*args) == expected_result @pytest.mark.parametrize( "args, expected_result", (((date(2020, 10, 1),), 1601510400), ((date(2019, 5, 4), 6), 1556949600)) ) async def test_date_to_timestamp(args, expected_result, loop): """Tests for date_to_timestamp.""" assert legacy_utils.date_to_timestamp(*args) == expected_result @pytest.mark.parametrize( "args, expected_result", ( ((date(2020, 10, 1), date(2020, 10, 2)), [date(2020, 10, 1), date(2020, 10, 2)]), ( (date(2019, 5, 4), date(2019, 5, 7)), [date(2019, 5, 4), date(2019, 5, 5), date(2019, 5, 6), date(2019, 5, 7)], ), ((date(2020, 10, 1), date(2020, 10, 2), True), [1601510400, 1601596800]), ((date(2019, 5, 4), date(2019, 5, 2)), []), ), ) async def test_dates_range(args, expected_result, loop): """Tests for dates_range.""" assert list(legacy_utils.dates_range(*args)) == expected_result @pytest.mark.parametrize( "args, expected_result", ((([1, 2, 3, 4],), [[1], [2], [3], [4]]), (([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]])) ) async def test_iter_chunk(args, expected_result, loop): """Tests for iter_chunk.""" assert list(legacy_utils.iter_chunk(*args)) == expected_result @pytest.mark.parametrize( "args,kwargs,arg_name,from_default,expected_result", ( (([1, 2], 3, "a"), {}, "arg_1", False, [1, 2]), (([1], 3, "a"), {}, "arg_2", False, 3), (([1], 3, "a"), {}, "arg_3", False, "a"), (([1],), {"arg_2": 3, "arg_3": "a"}, "arg_3", False, "a"), (([1],), {"arg_3": "a"}, "arg_2", False, None), (([1],), {"arg_3": "a"}, "arg_2", True, 10), (([1],), {"arg_3": "a"}, "arg_4", True, "bb"), ), ) async def test_get_arg(args, kwargs, arg_name, from_default, expected_result, loop): """Tests for get_arg.""" def func(arg_1: List[int], arg_2: int = 10, arg_3: str = "aa", arg_4: str = "bb"): pass args_spec = getfullargspec(func) assert get_arg(args, kwargs, args_spec, arg_name, from_default) == expected_result @pytest.mark.parametrize( "args,kwargs,arg_name,value,expected_result", ( (([1, 2], 3, "a"), {}, "arg_1", [2], (([2], 3, "a"), {})), (([1], 3, "a"), {}, "arg_2", 40, (([1], 40, "a"), {})), (([1],), {"arg_2": 3, "arg_3": "a"}, "arg_3", "c", (([1],), {"arg_2": 3, "arg_3": "c"})), (([1],), {"arg_4": "a"}, "arg_3", "d", (([1],), {"arg_3": "d", "arg_4": "a"})), ), ) async def test_set_arg(args, kwargs, arg_name, value, expected_result, loop): """Tests for set_arg.""" def func(arg_1: List[int], arg_2: int = 10, arg_3: str = "aa", arg_4: str = "bb"): pass args_spec = getfullargspec(func) assert set_arg(args, kwargs, args_spec, arg_name, value) == expected_result @pytest.mark.parametrize("items_count,page_size,in_parallel", ((20, 5, False), (21, 5, True), (10, 20, True))) async def test_delphi_pagination(items_count, page_size, in_parallel, loop): """Tests for delphi_pagination.""" def get_items(limit: int, offset: int = 0): return [i for i in range(offset, offset + limit)] async def func(arg_1: List[int], arg_2: int, limit: int, offset: int, arg_3: str = "aa", group_by: str = None): limit = min(items_count - offset, limit) return {"items": get_items(limit, offset), "count": limit} decorated_func = delphi_pagination(page_size, in_parallel=in_parallel)(func) result = await decorated_func([10, 20], 300) assert result == get_items(items_count) @pytest.mark.parametrize( "market,global_market,expected_result", ( ("ca", DELPHI_GLOBAL_MARKET, "ca"), ("Ca", "", "ca"), ("GB", GLOBAL_MARKET_CODE, "gb"), (DELPHI_GLOBAL_MARKET, GLOBAL_MARKET_CODE, GLOBAL_MARKET_CODE), (DELPHI_GLOBAL_MARKET, GLOBAL_MARKET, GLOBAL_MARKET), (GLOBAL_MARKET, "", ""), (GLOBAL_MARKET_CODE, "us", "us"), ), ) async def test_convert_market(market, global_market, expected_result, loop): """Tests for convert_market.""" assert legacy_utils.convert_market(market, global_market) == expected_result async def request_per_item_func(par1: list or int = None, par2: list or int = None, result_type: str = "l"): param = par1 or par2 if not param: param = [100] if not isinstance(param, list): param = [param] result = [] if result_type == "l" else {} for item in param: if result_type == "l": result += [item, item * 2] else: result.update({f"i{item}": item, f"i{item * 2}": item * 2}) return result async def modify_items_func(value, *args, **kwargs): return [1, 4, 6] @pytest.mark.parametrize( "default_value,actual_value,modify_items_func,sum_func,sum_all_func,result_type,single_item", ( (None, None, None, None, None, "l", False), (None, [1, 3, 5], None, None, None, "l", False), ([2, 6, 10], None, None, None, None, "l", False), ([2, 6, 10], [1, 3, 5], None, None, None, "l", False), (None, [1, 3, 5], None, lambda d1, d2, _args, _kwargs: {**d1, **d2}, None, "d", False), ([3, 4, 5], None, None, lambda d1, d2, _args, _kwargs: {**d1, **d2}, None, "d", False), (None, [1, 3, 5], None, None, None, "l", True), (None, 1, None, None, None, "l", False), (None, [1, 3, 5], None, None, lambda d_l, *a, **k: {k: v for d in d_l for k, v in d.items()}, "d", False), (None, 1, modify_items_func, None, None, "l", False), ), ) async def test_request_per_item( default_value, actual_value, modify_items_func, sum_func, sum_all_func, result_type, single_item, loop, mocker ): """Tests for request_per_item.""" mocked_func = mocker.Mock(side_effect=request_per_item_func) mocked_func.__signature__ = signature(request_per_item_func) decorated_func = request_per_item( "par1", default_value=default_value, modify_items_func=modify_items_func, sum_func=sum_func, sum_all_func=sum_all_func, result_cls=(dict if sum_func else list), single_item_call=single_item, )(mocked_func) value = actual_value or default_value if value and not isinstance(value, (list, tuple, set)): value = [value] result = await decorated_func(actual_value, result_type=result_type) if modify_items_func: value = await modify_items_func(value) assert result == await request_per_item_func(value, result_type=result_type) if value: assert mocked_func.call_count == len(value) assert mocked_func.call_args_list == [ ((i if single_item else [i],), {"result_type": result_type}) for i in value ] else: assert mocked_func.call_count == 1 assert mocked_func.call_args == ((None,), {"result_type": result_type}) @pytest.mark.parametrize( "field_name,arg_field,actual_value,sum_func,result_type,single_item,exception", ( (("par1", "par2"), "par1", [1, 3, 5], None, "l", True, None), (("par1", "par2"), "par2", [1, 3, 5], None, "l", False, None), (("par1", "par2"), "par2", 3, None, "l", True, None), (("par1",), "par1", None, None, "l", False, ValueError), (("par1",), "par1", [1, 3, 5], None, "l", False, None), (("par2",), "par2", [1, 3, 5], lambda d1, d2, _args, _kwargs: {**d1, **d2}, "d", False, None), ), ) async def test_request_per_item_many( field_name, arg_field, actual_value, sum_func, result_type, single_item, exception, loop, mocker ): """Tests for request_per_item.""" mocked_func = mocker.Mock(side_effect=request_per_item_func) mocked_func.__signature__ = signature(request_per_item_func) decorated_func = request_per_item_many(field_name, dict if sum_func else list, sum_func, single_item)(mocked_func) if actual_value and not isinstance(actual_value, (list, tuple, set)): actual_value = [actual_value] if exception: with pytest.raises(exception): await decorated_func(**{arg_field: actual_value, "result_type": result_type}) else: result = await decorated_func(**{arg_field: actual_value, "result_type": result_type}) assert result == await request_per_item_func(actual_value, result_type=result_type) if actual_value: assert mocked_func.call_count == len(actual_value) assert mocked_func.call_args_list == [ ((), {arg_field: (i if single_item else [i]), "result_type": result_type}) for i in actual_value ] else: assert mocked_func.call_count == 1 assert mocked_func.call_args == ((), {arg_field: None, "result_type": result_type}) @pytest.mark.parametrize( "vendor,playlist_id,expected_result", ( (APPLE, "1234", f"{APPLE}_1234"), (APPLE, "1234ab", f"{APPLE}_pl.1234ab"), (APPLE, "ra.1234", f"{APPLE}_1234"), (APPLE, "pl.1234ab", f"{APPLE}_pl.1234ab"), (APPLE, "pl.1234", f"{APPLE}_pl.1234"), (APPLE, f"{APPLE}_1234ab", f"{APPLE}_1234ab"), (APPLE, f"{APPLE}_pl.1234ab", f"{APPLE}_pl.1234ab"), (AMAZON, f"{AMAZON}_1234", f"{AMAZON}_1234"), (AMAZON, "B01JGDPEMA:9337_gb", f"{AMAZON}_B01JGDPEMA"), (SPOTIFY, "1234", f"{SPOTIFY}_1234"), (SPOTIFY, "1234abc", f"{SPOTIFY}_1234abc"), (SPOTIFY, f"{SPOTIFY}_1234", f"{SPOTIFY}_1234"), (SPOTIFY, f"{SPOTIFY_URI_PREFIX}1234", f"{SPOTIFY}_1234"), (SPOTIFY, f"{SPOTIFY_URI_PREFIX}1234abc", f"{SPOTIFY}_1234abc"), ), ) async def test_format_delphi_playlist_id(vendor, playlist_id, expected_result, loop): """Tests for format_delphi_playlist_id.""" assert legacy_utils.format_delphi_playlist_id(vendor, playlist_id) == expected_result