diff --git a/fpl/fpl.py b/fpl/fpl.py index 73aac09..46ff88c 100644 --- a/fpl/fpl.py +++ b/fpl/fpl.py @@ -59,6 +59,7 @@ async def get_user(self, user_id, return_json=False): :type return_json: bool :rtype: :class:`User` or `dict` """ + assert int(user_id) > 0, "User ID must be a positive number." url = API_URLS["user"].format(user_id) user = await fetch(self.session, url) @@ -66,7 +67,7 @@ async def get_user(self, user_id, return_json=False): return user return User(user, session=self.session) - async def get_teams(self, team_ids=[], return_json=False): + async def get_teams(self, team_ids=None, return_json=False): """Returns either a list of *all* teams, or a list of teams with IDs in the optional ``team_ids`` list. @@ -85,6 +86,7 @@ async def get_teams(self, team_ids=[], return_json=False): teams = await fetch(self.session, url) if team_ids: + team_ids = set(team_ids) teams = [team for team in teams if team["id"] in team_ids] if return_json: @@ -131,6 +133,7 @@ async def get_team(self, team_id, return_json=False): 19 - West Ham 20 - Wolves """ + assert 0 < int(team_id) < 21, "Team ID must be a number between 1 and 20." url = API_URLS["teams"] teams = await fetch(self.session, url) team = next(team for team in teams if team["id"] == int(team_id)) @@ -153,6 +156,7 @@ async def get_player_summary(self, player_id, return_json=False): :type return_json: bool :rtype: :class:`PlayerSummary` or ``dict`` """ + assert int(player_id) > 0, "Player's ID must be a positive number" url = API_URLS["player"].format(player_id) player_summary = await fetch(self.session, url) @@ -161,20 +165,23 @@ async def get_player_summary(self, player_id, return_json=False): return PlayerSummary(player_summary) - async def get_player_summaries(self, player_ids=[], return_json=False): - """Returns either a list of summaries of *all* players, or a list of - summaries of players whose ID are in the ``player_ids`` list. + async def get_player_summaries(self, player_ids, return_json=False): + """Returns a list of summaries of players whose ID are + in the ``player_ids`` list. Information is taken from e.g.: https://fantasy.premierleague.com/drf/element-summary/1 - :param list player_ids: (optional) A list of player IDs. + :param list player_ids: A list of player IDs. :param return_json: (optional) Boolean. If ``True`` returns a list of ``dict``s, if ``False`` returns a list of :class:`PlayerSummary` objects. Defaults to ``False``. :type return_json: bool :rtype: list """ + if not player_ids: + return [] + tasks = [asyncio.ensure_future( fetch(self.session, API_URLS["player"].format(player_id))) for player_id in player_ids] @@ -204,12 +211,16 @@ async def get_player(self, player_id, players=None, include_summary=False, if ``False`` returns a :class:`Player` object. Defaults to ``False``. :rtype: :class:`Player` or ``dict`` + :raises ValueError: Player with ``player_id`` not found """ if not players: players = await fetch(self.session, API_URLS["players"]) - player = next(player for player in players - if player["id"] == player_id) + try: + player = next(player for player in players + if player["id"] == player_id) + except StopIteration: + raise ValueError(f"Player with ID {player_id} not found") if include_summary: player_summary = await self.get_player_summary( @@ -221,7 +232,7 @@ async def get_player(self, player_id, players=None, include_summary=False, return Player(player) - async def get_players(self, player_ids=[], include_summary=False, + async def get_players(self, player_ids=None, include_summary=False, return_json=False): """Returns either a list of *all* players, or a list of players whose IDs are in the given ``player_ids`` list. @@ -264,19 +275,26 @@ async def get_fixture(self, fixture_id, return_json=False): ``False``. :type return_json: bool :rtype: :class:`Fixture` or ``dict`` + :raises ValueError: if fixture with ``fixture_id`` not found """ fixtures = await fetch(self.session, API_URLS["fixtures"]) - fixture = next(fixture for fixture in fixtures - if fixture["id"] == fixture_id) + try: + fixture = next(fixture for fixture in fixtures + if fixture["id"] == fixture_id) + except StopIteration: + raise ValueError(f"Fixture with ID {fixture_id} not found") fixture_gameweek = fixture["event"] gameweek_fixtures = await fetch( self.session, API_URLS["gameweek_fixtures"].format(fixture_gameweek)) - fixture = next(fixture for fixture in gameweek_fixtures - if fixture["id"] == fixture_id) + try: + fixture = next(fixture for fixture in gameweek_fixtures + if fixture["id"] == fixture_id) + except StopIteration: + raise ValueError(f"Fixture with ID {fixture_id} not found in gameweek fixtures") if return_json: return fixture @@ -298,6 +316,9 @@ async def get_fixtures_by_id(self, fixture_ids, return_json=False): :type return_json: bool :rtype: list """ + if not fixture_ids: + return [] + fixtures = await fetch(self.session, API_URLS["fixtures"]) fixture_gameweeks = set(fixture["event"] for fixture in fixtures if fixture["id"] in fixture_ids) @@ -386,8 +407,11 @@ async def get_gameweek(self, gameweek_id, include_live=False, """ static_gameweeks = await fetch(self.session, API_URLS["gameweeks"]) - static_gameweek = next(gameweek for gameweek in static_gameweeks if - gameweek["id"] == gameweek_id) + try: + static_gameweek = next(gameweek for gameweek in static_gameweeks if + gameweek["id"] == gameweek_id) + except StopIteration: + raise ValueError(f"Gameweek with ID {gameweek_id} not found") live_gameweek = await fetch( self.session, API_URLS["gameweek_live"].format(gameweek_id)) @@ -483,14 +507,16 @@ async def get_h2h_league(self, league_id, return_json=False): async def login(self, email=None, password=None): """Returns a requests session with FPL login authentication. - :param string user: Email address for the user's Fantasy Premier League + :param string email: Email address for the user's Fantasy Premier League account. :param string password: Password for the user's Fantasy Premier League account. """ if not email and not password: - email = os.environ["FPL_EMAIL"] - password = os.environ["FPL_PASSWORD"] + email = os.getenv("FPL_EMAIL", None) + password = os.getenv("FPL_PASSWORD", None) + if not email or not password: + raise ValueError("Email and password must be set") url = "https://fantasy.premierleague.com/" await self.session.get(url) diff --git a/fpl/models/fixture.py b/fpl/models/fixture.py index d49575e..fa659e1 100644 --- a/fpl/models/fixture.py +++ b/fpl/models/fixture.py @@ -35,19 +35,20 @@ def _get_players(self, metric): """Helper function that returns a dictionary containing players for the given metric (away and home). """ - for statistic in self.stats: + stats = getattr(self, "stats", []) + for statistic in stats: if metric in statistic.keys(): - player_information = statistic[metric] + return statistic[metric] - return player_information + return {} def get_goalscorers(self): """Returns all players who scored in the fixture. :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("goals_scored") @@ -56,8 +57,8 @@ def get_assisters(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("assists") @@ -66,8 +67,8 @@ def get_own_goalscorers(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("own_goals") @@ -76,8 +77,8 @@ def get_yellow_cards(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("yellow_cards") @@ -86,8 +87,8 @@ def get_red_cards(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("red_cards") @@ -96,8 +97,8 @@ def get_penalty_saves(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("penalties_saved") @@ -106,8 +107,8 @@ def get_penalty_misses(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("penalties_missed") @@ -116,8 +117,8 @@ def get_saves(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("saves") @@ -126,8 +127,8 @@ def get_bonus(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("bonus") @@ -136,8 +137,8 @@ def get_bps(self): :rtype: dict """ - if not self.finished: - return + if not getattr(self, "finished", False): + return {} return self._get_players("bps") diff --git a/fpl/models/h2h_league.py b/fpl/models/h2h_league.py index 714cefb..87939ce 100644 --- a/fpl/models/h2h_league.py +++ b/fpl/models/h2h_league.py @@ -1,7 +1,7 @@ import asyncio from ..constants import API_URLS -from ..utils import fetch, get_current_gameweek +from ..utils import fetch, get_current_gameweek, logged_in class H2HLeague(): @@ -23,7 +23,7 @@ class H2HLeague(): >>> asyncio.run(main()) League 760869 - 760869 """ - def __init__(self, league_information, session=None): + def __init__(self, league_information, session): self._session = session for k, v in league_information.items(): @@ -42,6 +42,9 @@ async def get_fixtures(self, gameweek=None): if not self._session: return + if not logged_in(self._session): + raise Exception("Not authorized to get h2h fixtures. Log in.") + if gameweek: gameweeks = range(gameweek, gameweek + 1) else: diff --git a/fpl/models/player.py b/fpl/models/player.py index 93e554a..244d219 100644 --- a/fpl/models/player.py +++ b/fpl/models/player.py @@ -30,7 +30,7 @@ def games_played(self): :rtype: int """ - return sum([1 for fixture in self.fixtures if fixture["minutes"] > 0]) + return sum([1 for fixture in getattr(self, "fixtures", []) if fixture["minutes"] > 0]) @property def pp90(self): @@ -38,9 +38,10 @@ def pp90(self): :rtype: float """ - if self.minutes == 0: + minutes = getattr(self, "minutes", 0) + if minutes == 0: return 0 - return self.total_points / float(self.minutes) + return getattr(self, "total_points", 0) / float(minutes) def __str__(self): return (f"{self.web_name} - " diff --git a/fpl/models/user.py b/fpl/models/user.py index 6e260b0..7c32f8f 100644 --- a/fpl/models/user.py +++ b/fpl/models/user.py @@ -1,7 +1,7 @@ import asyncio from ..constants import API_URLS -from ..utils import fetch +from ..utils import fetch, logged_in def valid_gameweek(gameweek): @@ -9,9 +9,11 @@ def valid_gameweek(gameweek): :param gameweek: The gameweek. :type gameweek: int or string + :raises ValueError: if gameweek is not a number between 1 and 38 """ - if not isinstance(gameweek, int) and (gameweek < 1 or gameweek > 38): - raise "Gameweek must be a number between 1 and 38." + gameweek = int(gameweek) + if (gameweek < 1) or (gameweek > 38): + raise ValueError("Gameweek must be a number between 1 and 38.") return True @@ -70,7 +72,7 @@ async def get_season_history(self): :rtype: list """ if hasattr(self, "_history"): - history = self._history["season"] + history = self._history else: history = await fetch( self._session, API_URLS["user_history"].format(self.id)) @@ -126,7 +128,7 @@ async def get_picks(self, gameweek=None): return next(pick["picks"] for pick in picks if pick["event"]["id"] == gameweek) - return [pick["picks"] for pick in picks] + return [p for pick in picks for p in pick["picks"]] async def get_active_chips(self, gameweek=None): """Returns a list containing the user's active chips each gameweek. @@ -179,7 +181,7 @@ async def get_automatic_substitutions(self, gameweek=None): return next(pick["automatic_subs"] for pick in picks if pick["event"]["id"] == gameweek) - return [pick["automatic_subs"] for pick in picks] + return [p for pick in picks for p in pick["automatic_subs"]] async def get_team(self): """Returns a logged in user's current team. Requires the user to have @@ -190,8 +192,8 @@ async def get_team(self): :rtype: list """ - if not self._session: - raise "User must be logged in." + if not logged_in(self._session): + raise Exception("User must be logged in.") response = await fetch( self._session, API_URLS["user_team"].format(self.id)) @@ -211,13 +213,11 @@ async def get_transfers(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list """ - if hasattr(self, "_transfers"): - return self._transfers["history"] - - transfers = await fetch( - self._session, API_URLS["user_transfers"].format(self.id)) - - self._transfers = transfers + transfers = getattr(self, "_transfers", None) + if not transfers: + transfers = await fetch( + self._session, API_URLS["user_transfers"].format(self.id)) + self._transfers = transfers if gameweek: valid_gameweek(gameweek) @@ -253,8 +253,8 @@ async def get_watchlist(self): :rtype: list """ - if not self._session: - raise "User must be logged in." + if not logged_in(self._session): + raise Exception("User must be logged in.") return await fetch(self._session, API_URLS["watchlist"]) diff --git a/fpl/utils.py b/fpl/utils.py index 84f740e..38ebf3e 100644 --- a/fpl/utils.py +++ b/fpl/utils.py @@ -85,3 +85,14 @@ def scale(value, upper, lower, min_, max_): def average(iterable): """Returns the average value of the iterable.""" return sum(iterable) / float(len(iterable)) + + +def logged_in(session): + """Checks that the user is logged in within the session. + + :param session: http session + :type session: aiohttp.ClientSession + :return: True if user is logged in else False + :rtype: bool + """ + return "csrftoken" in session.cookie_jar.filter_cookies("https://users.premierleague.com/") \ No newline at end of file diff --git a/setup.py b/setup.py index 996ffd4..eb598bc 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ "appdirs", "aiohttp", "pytest-aiohttp", + "pytest-mock", "pytest", ], entry_points=""" diff --git a/tests/conftest.py b/tests/conftest.py index e61ca0a..a3f3ad6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,10 @@ import pytest from fpl import FPL +from fpl.models import Fixture, H2HLeague, User +from tests.test_fixture import fixture_data +from tests.test_h2h_league import h2h_league_data +from tests.test_user import user_data @pytest.fixture() @@ -21,15 +25,6 @@ async def classic_league(): await session.close() -@pytest.fixture() -async def fixture(): - session = aiohttp.ClientSession() - fpl = FPL(session) - fixture = await fpl.get_fixture(6) - yield fixture - await session.close() - - @pytest.fixture() async def gameweek(): session = aiohttp.ClientSession() @@ -39,16 +34,6 @@ async def gameweek(): await session.close() -@pytest.fixture() -async def h2h_league(): - session = aiohttp.ClientSession() - fpl = FPL(session) - await fpl.login() - h2h_league = await fpl.get_h2h_league(760869) - yield h2h_league - await session.close() - - @pytest.fixture() async def player(): session = aiohttp.ClientSession() @@ -77,10 +62,19 @@ async def team(): @pytest.fixture() -async def user(): +def fixture(): + return Fixture(fixture_data) + + +@pytest.fixture() +async def h2h_league(): session = aiohttp.ClientSession() - fpl = FPL(session) - await fpl.login() - user = await fpl.get_user(3808385) - yield user + yield H2HLeague(h2h_league_data, session) await session.close() + + +@pytest.fixture() +async def user(): + session = aiohttp.ClientSession() + yield User(user_data, session) + await session.close() \ No newline at end of file diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 0000000..e677f39 --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,6 @@ +from unittest.mock import MagicMock + + +class AsyncMock(MagicMock): + async def __call__(self, *args, **kwargs): + return super(AsyncMock, self).__call__(*args, **kwargs) \ No newline at end of file diff --git a/tests/test_classic_league.py b/tests/test_classic_league.py index 7b4b3e4..40f6496 100644 --- a/tests/test_classic_league.py +++ b/tests/test_classic_league.py @@ -1,4 +1,28 @@ +import aiohttp + +from fpl.models.classic_league import ClassicLeague + + class TestClassicLeague(object): + async def test_init(self, loop): + data = { + "new_entries": {"has_next": False, "number": 1, "results": []}, + "league": { + "id": 1, + "leagueban_set": [], + "name": "Arsenal", + "short_name": "team-1", + "created": "2018-07-05T12:12:23Z", + "closed": False, + }, + } + session = aiohttp.ClientSession() + classic_league = ClassicLeague(data, session) + assert classic_league._session is session + for k, v in data.items(): + assert getattr(classic_league, k) == v + await session.close() + async def test_classic_league(self, loop, classic_league): assert classic_league.__str__() == "Steem Fantasy League - 633353" diff --git a/tests/test_fixture.py b/tests/test_fixture.py index 92d368e..ed22dfd 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -1,40 +1,178 @@ +from fpl.models.fixture import Fixture + +fixture_data = { + "id": 6, + "kickoff_time_formatted": "10 Aug 20:00", + "started": True, + "event_day": 1, + "deadline_time": "2018-08-10T18:00:00Z", + "deadline_time_formatted": "10 Aug 19:00", + "stats": [ + { + "goals_scored": { + "a": [{"value": 1, "element": 234}], + "h": [{"value": 1, "element": 286}, {"value": 1, "element": 302}], + } + }, + { + "assists": { + "a": [{"value": 1, "element": 221}], + "h": [{"value": 1, "element": 295}, {"value": 1, "element": 297}], + } + }, + {"own_goals": {"a": [], "h": []}}, + {"penalties_saved": {"a": [], "h": []}}, + {"penalties_missed": {"a": [], "h": []}}, + { + "yellow_cards": { + "a": [{"value": 1, "element": 226}], + "h": [{"value": 1, "element": 304}, {"value": 1, "element": 481}], + } + }, + {"red_cards": {"a": [], "h": []}}, + { + "saves": { + "a": [{"value": 4, "element": 213}], + "h": [{"value": 3, "element": 282}], + } + }, + { + "bonus": { + "a": [{"value": 1, "element": 234}], + "h": [{"value": 3, "element": 286}, {"value": 2, "element": 302}], + } + }, + { + "bps": { + "a": [ + {"value": 25, "element": 234}, + {"value": 23, "element": 221}, + {"value": 16, "element": 213}, + {"value": 16, "element": 215}, + {"value": 15, "element": 225}, + {"value": 14, "element": 220}, + {"value": 13, "element": 227}, + {"value": 13, "element": 231}, + {"value": 12, "element": 219}, + {"value": 10, "element": 233}, + {"value": 6, "element": 226}, + {"value": 5, "element": 228}, + {"value": 3, "element": 492}, + {"value": 2, "element": 236}, + ], + "h": [ + {"value": 30, "element": 286}, + {"value": 29, "element": 302}, + {"value": 24, "element": 297}, + {"value": 22, "element": 295}, + {"value": 16, "element": 289}, + {"value": 15, "element": 282}, + {"value": 15, "element": 292}, + {"value": 13, "element": 291}, + {"value": 13, "element": 305}, + {"value": 13, "element": 481}, + {"value": 8, "element": 304}, + {"value": 4, "element": 298}, + {"value": 3, "element": 303}, + {"value": -2, "element": 306}, + ], + } + }, + ], + "team_h_difficulty": 3, + "team_a_difficulty": 4, + "code": 987597, + "kickoff_time": "2018-08-10T19:00:00Z", + "team_h_score": 2, + "team_a_score": 1, + "finished": True, + "minutes": 90, + "provisional_start_time": False, + "finished_provisional": True, + "event": 1, + "team_a": 11, + "team_h": 14, +} + + class TestFixture(object): - def test_get_goalscorers(self, loop, fixture): - goalscorers = fixture.get_goalscorers() - assert isinstance(goalscorers, dict) + def test_init(self): + fixture = Fixture(fixture_data) + for k, v in fixture_data.items(): + assert getattr(fixture, k) == v + + @staticmethod + def _do_test_not_finished(fixture, method): + delattr(fixture, "finished") + data_dict = getattr(fixture, method)() + assert isinstance(data_dict, dict) + assert len(data_dict) == 0 + + @staticmethod + def _do_test_finished(fixture, method): + data_dict = getattr(fixture, method)() + assert isinstance(data_dict, dict) + assert len(data_dict) == 2 + + def test_get_goalscorers_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_goalscorers") + + def test_get_goalscorers_finished(self, fixture): + self._do_test_finished(fixture, "get_goalscorers") + + def test_get_assisters_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_assisters") + + def test_get_assisters_finished(self, fixture): + self._do_test_finished(fixture, "get_assisters") + + def test_get_own_goalscorers_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_own_goalscorers") + + def test_get_own_goalscorers_finished(self, fixture): + self._do_test_finished(fixture, "get_own_goalscorers") + + def test_get_yellow_cards_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_yellow_cards") + + def test_get_yellow_cards(self, fixture): + self._do_test_finished(fixture, "get_yellow_cards") + + def test_get_red_cards_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_red_cards") + + def test_get_red_cards_finished(self, fixture): + self._do_test_finished(fixture, "get_red_cards") + + def test_get_penalty_saves_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_penalty_saves") + + def test_get_penalty_saves_finished(self, fixture): + self._do_test_finished(fixture, "get_penalty_saves") - def test_get_assisters(self, loop, fixture): - assisters = fixture.get_assisters() - assert isinstance(assisters, dict) + def test_get_penalty_misses_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_penalty_misses") - def test_get_own_goalscorers(self, loop, fixture): - own_goalscorers = fixture.get_own_goalscorers() - assert isinstance(own_goalscorers, dict) + def test_get_penalty_misses_finished(self, fixture): + self._do_test_finished(fixture, "get_penalty_misses") - def test_get_yellow_cards(self, loop, fixture): - yellow_cards = fixture.get_yellow_cards() - assert isinstance(yellow_cards, dict) + def test_get_saves_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_saves") - def test_get_red_cards(self, loop, fixture): - red_cards = fixture.get_red_cards() - assert isinstance(red_cards, dict) + def test_get_saves_finished(self, fixture): + self._do_test_finished(fixture, "get_saves") - def test_get_penalty_saves(self, loop, fixture): - penalty_saves = fixture.get_penalty_saves() - assert isinstance(penalty_saves, dict) + def test_get_bonus_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_bonus") - def test_get_penalty_misses(self, loop, fixture): - penalty_misses = fixture.get_penalty_misses() - assert isinstance(penalty_misses, dict) + def test_get_bonus_finished(self, fixture): + self._do_test_finished(fixture, "get_bonus") - def test_get_saves(self, loop, fixture): - saves = fixture.get_saves() - assert isinstance(saves, dict) + def test_get_bps_not_finished(self, fixture): + self._do_test_not_finished(fixture, "get_bps") - def test_get_bonus(self, loop, fixture): - bonus = fixture.get_bonus() - assert isinstance(bonus, dict) + def test_get_bps_finished(self, fixture): + self._do_test_finished(fixture, "get_bps") - def test_get_bps(self, loop, fixture): - bps = fixture.get_bps() - assert isinstance(bps, dict) + def test_str(self, fixture): + assert str(fixture) == "Man Utd vs. Leicester - 10 Aug 19:00" diff --git a/tests/test_fpl.py b/tests/test_fpl.py index df0511c..83fe714 100644 --- a/tests/test_fpl.py +++ b/tests/test_fpl.py @@ -1,3 +1,4 @@ +import aiohttp import pytest from fpl import FPL @@ -8,17 +9,39 @@ from fpl.models.player import Player, PlayerSummary from fpl.models.team import Team from fpl.models.user import User +from tests.helper import AsyncMock class TestFPL(object): + async def test_init(self, loop): + session = aiohttp.ClientSession() + fpl = FPL(session) + assert fpl.session is session + await session.close() + async def test_user(self, loop, fpl): + # test negative id + with pytest.raises(AssertionError): + await fpl.get_user(-10) + + with pytest.raises(AssertionError): + await fpl.get_user("-10") + + # test valid id user = await fpl.get_user("3523615") assert isinstance(user, User) + # test valid id, require json response user = await fpl.get_user("3523615", True) assert isinstance(user, dict) async def test_team(self, loop, fpl): + # test team id out of valid range + with pytest.raises(AssertionError): + await fpl.get_team(0) + with pytest.raises(AssertionError): + await fpl.get_team(21) + team = await fpl.get_team(1) assert isinstance(team, Team) @@ -43,6 +66,10 @@ async def test_teams(self, loop, fpl): assert [team.id for team in teams] == [1, 2, 3] async def test_player_summary(self, loop, fpl): + # test non positive id + with pytest.raises(AssertionError): + await fpl.get_player_summary(0) + player_summary = await fpl.get_player_summary(123) assert isinstance(player_summary, PlayerSummary) @@ -50,6 +77,11 @@ async def test_player_summary(self, loop, fpl): assert isinstance(player_summary, dict) async def test_player_summaries(self, loop, fpl): + # test no specified IDs + player_summaries = await fpl.get_player_summaries([]) + assert isinstance(player_summaries, list) + assert len(player_summaries) == 0 + player_summaries = await fpl.get_player_summaries([1, 2, 3]) assert isinstance(player_summaries, list) assert isinstance(player_summaries[0], PlayerSummary) @@ -59,6 +91,10 @@ async def test_player_summaries(self, loop, fpl): assert isinstance(player_summaries[0], dict) async def test_player(self, loop, fpl): + # test invalid ID + with pytest.raises(ValueError): + await fpl.get_player(-1) + player = await fpl.get_player(1) assert isinstance(player, Player) @@ -86,6 +122,10 @@ async def test_players(self, loop, fpl): assert isinstance(players[0].fixtures, list) async def test_fixture(self, loop, fpl): + # test fixture with unknown id + with pytest.raises(ValueError): + await fpl.get_fixture(0) + fixture = await fpl.get_fixture(6) assert isinstance(fixture, Fixture) @@ -93,6 +133,11 @@ async def test_fixture(self, loop, fpl): assert isinstance(fixture, dict) async def test_fixtures_by_id(self, loop, fpl): + # test empty fixture ids + fixtures = await fpl.get_fixtures_by_id([]) + assert isinstance(fixtures, list) + assert len(fixtures) == 0 + fixtures = await fpl.get_fixtures_by_id([100, 200, 300]) assert isinstance(fixtures, list) assert isinstance(fixtures[0], Fixture) @@ -160,14 +205,33 @@ async def test_h2h_league(self, loop, fpl): h2h_league = await fpl.get_h2h_league(760869, True) assert isinstance(h2h_league, dict) - async def test_login(self, loop, fpl): + async def test_login_with_no_email_password(self, loop, mocker, monkeypatch, fpl): + mocked_text = mocker.patch('aiohttp.ClientResponse.text', new_callable=AsyncMock) + monkeypatch.setenv("FPL_EMAIL", "") + monkeypatch.setenv("FPL_PASSWORD", "") + with pytest.raises(ValueError): + await fpl.login() + mocked_text.assert_not_called() + + async def test_login_with_invalid_email_password(self, loop, mocker, monkeypatch, fpl): + mocked_text = mocker.patch('aiohttp.ClientResponse.text', new_callable=AsyncMock) + mocked_text.return_value = "Incorrect email or password" + with pytest.raises(ValueError): await fpl.login(123, 123) + assert mocked_text.call_count == 1 - await fpl.login() - user = await fpl.get_user(3808385) - team = await user.get_team() - assert isinstance(team, list) + monkeypatch.setenv("FPL_EMAIL", 123) + monkeypatch.setenv("FPL_PASSWORD", 123) + with pytest.raises(ValueError): + await fpl.login() + assert mocked_text.call_count == 2 + + async def test_login_with_valid_email_password(self, loop, mocker, fpl): + mocked_text = mocker.patch('aiohttp.ClientResponse.text', new_callable=AsyncMock) + mocked_text.return_value = "Successful login" + await fpl.login("email", "password") + mocked_text.assert_called_once() async def test_points_against(self, loop, fpl): points_against = await fpl.get_points_against() diff --git a/tests/test_h2h_league.py b/tests/test_h2h_league.py index 6d3b9ba..49c85ab 100644 --- a/tests/test_h2h_league.py +++ b/tests/test_h2h_league.py @@ -1,8 +1,84 @@ +import aiohttp +import pytest + +from fpl.models.h2h_league import H2HLeague +from tests.helper import AsyncMock + +h2h_league_data = { + "new_entries": {"has_next": False, "number": 1, "results": []}, + "league": { + "id": 829116, + "leagueban_set": [], + "name": "League 829116", + "has_started": True, + "can_delete": False, + "short_name": None, + "created": "2018-08-09T18:10:37Z", + "closed": True, + "forum_disabled": False, + "make_code_public": False, + "rank": None, + "size": None, + "league_type": "c", + "_scoring": "h", + "ko_rounds": 2, + "admin_entry": None, + "start_event": 1, + }, + "standings": {"has_next": False, "number": 1, "results": []}, + "matches_next": {}, + "matches_this": {}, +} + + class TestH2HLeague(object): + async def test_init(self, loop): + session = aiohttp.ClientSession() + league = H2HLeague(h2h_league_data, session) + assert league._session == session + for k, v in h2h_league_data.items(): + assert getattr(league, k) == v + await session.close() + def test_h2h_league(self, loop, h2h_league): - assert h2h_league.__str__() == "League 760869 - 760869" + assert h2h_league.__str__() == "League 829116 - 829116" + + async def test_get_fixtures_with_known_gameweek_unauthorized( + self, loop, h2h_league): + with pytest.raises(Exception): + await h2h_league.get_fixtures(1) + + async def test_get_fixtures_with_known_gameweek_authorized( + self, loop, mocker, h2h_league): + mocked_logged_in = mocker.patch( + "fpl.models.h2h_league.logged_in", return_value=True) + mocked_fetch = mocker.patch( + "fpl.models.h2h_league.fetch", return_value={}, new_callable=AsyncMock) + fixtures = await h2h_league.get_fixtures(1) + assert isinstance(fixtures, list) + assert len(fixtures) == 1 + mocked_logged_in.assert_called_once() + mocked_fetch.assert_called_once() + + async def test_get_fixtures_with_unknown_gameweek_unauthorized( + self, loop, h2h_league): + with pytest.raises(Exception): + await h2h_league.get_fixtures() - async def test_fixtures(self, loop, h2h_league): + async def test_get_fixtures_with_unknown_gameweek_authorized( + self, loop, mocker, h2h_league): + mocked_logged_in = mocker.patch( + "fpl.models.h2h_league.logged_in", return_value=True) + mocked_fetch = mocker.patch( + "fpl.models.h2h_league.fetch", return_value={}, new_callable=AsyncMock) + gameweek_number = 3 + mocked_current_gameweek = mocker.patch( + "fpl.models.h2h_league.get_current_gameweek", + return_value=gameweek_number, + new_callable=AsyncMock) fixtures = await h2h_league.get_fixtures() assert isinstance(fixtures, list) - assert isinstance(fixtures[0], dict) + assert len(fixtures) == gameweek_number + assert mocked_fetch.call_count == gameweek_number + mocked_logged_in.assert_called_once() + mocked_current_gameweek.assert_called_once() diff --git a/tests/test_user.py b/tests/test_user.py index 5ff163f..14c24ae 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,62 +1,351 @@ +import aiohttp +import pytest + +from fpl.models.user import User, valid_gameweek +from tests.helper import AsyncMock + +user_data = { + "entry": { + "id": 3808385, + "player_first_name": "Amos", + "player_last_name": "Bastian", + "player_region_id": 152, + "player_region_name": "Netherlands", + "player_region_short_iso": "NL", + "summary_overall_points": 1621, + "summary_overall_rank": 22424, + "summary_event_points": 75, + "summary_event_rank": 839850, + "joined_seconds": 16972, + "current_event": 26, + "total_transfers": 23, + "total_loans": 0, + "total_loans_active": 0, + "transfers_or_loans": "transfers", + "deleted": False, + "email": False, + "joined_time": "2018-08-09T22:44:21Z", + "name": "( ͡° ͜ʖ ͡°)", + "bank": 43, + "value": 1024, + "kit": "{\"kit_shirt_type\":\"plain\",\"kit_shirt_base\":\"#ff0000\",\"kit_shirt_sleeves\":\"#ff0000\",\"kit_shirt_secondary\":\"#e1e1e1\",\"kit_shirt_logo\":\"none\",\"kit_shorts\":\"#000000\",\"kit_socks_type\":\"plain\",\"kit_socks_base\":\"#ffffff\",\"kit_socks_secondary\":\"#e1e1e1\"}", + "event_transfers": 0, + "event_transfers_cost": 0, + "extra_free_transfers": 1, + "strategy": None, + "favourite_team": 14, + "started_event": 1, + "player": 7425806 + }, + "leagues": { + "cup": [ + + ], + "h2h": [ + + ], + "classic": [] + } +} + + +class TestHelpers: + def test_valid_gameweek_gameweek_out_of_range(self): + with pytest.raises(ValueError): + valid_gameweek(0) + with pytest.raises(ValueError): + valid_gameweek(39) + + def test_valid_gameweek_valid_gameweek(self): + assert valid_gameweek(1) is True + + class TestUser(object): - async def test_gameweek_history(self, loop, user): + async def test_init(self, loop): + session = aiohttp.ClientSession() + user = User(user_data, session) + assert user._session is session + assert user.leagues is user_data["leagues"] + for k, v in user_data["entry"].items(): + assert getattr(user, k) == v + await session.close() + + async def test_get_gameweek_history_unknown_gameweek_cached(self, loop, mocker, user): + user._history = {"history": [{"event": 1}, {"event": 2}, {"event": 3}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) history = await user.get_gameweek_history() + assert history is user._history["history"] + mocked_fetch.assert_not_called() + + async def test_get_gameweek_history_unknown_gameweek_non_cached(self, loop, mocker, user): + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"history": [{"event": 1}, {"event": 2}, {"event": 3}]}, + new_callable=AsyncMock) + history = await user.get_gameweek_history() + mocked_fetch.assert_called_once() assert isinstance(history, list) + assert len(history) == 3 + async def test_get_gameweek_history_known_gameweek_cached(self, loop, mocker, user): + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"history": []}, + new_callable=AsyncMock) + events = [{"event": 1}, {"event": 2}, {"event": 3}] + user._history = {"history": events} history = await user.get_gameweek_history(1) - assert isinstance(history, dict) + assert history is events[0] + mocked_fetch.assert_not_called() + + async def test_get_gameweek_history_known_gameweek_non_cached(self, loop, mocker, user): + events = [{"event": 1}, {"event": 2}, {"event": 3}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"history": events}, + new_callable=AsyncMock) + history = await user.get_gameweek_history(1) + assert history is events[0] + mocked_fetch.assert_called_once() + + async def test_get_season_history_cached(self, loop, mocker, user): + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"season": [{"season": 5}]}, + new_callable=AsyncMock) + seasons = [{"season": 6}] + user._history = {"season": seasons} + season_history = await user.get_season_history() + mocked_fetch.assert_not_called() + assert season_history is seasons - async def test_season_history(self, loop, user): + async def test_get_season_history_non_cached(self, loop, mocker, user): + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"season": [{"season": 5}]}, + new_callable=AsyncMock) season_history = await user.get_season_history() - assert isinstance(season_history, list) + mocked_fetch.assert_called_once() + assert season_history is mocked_fetch.return_value["season"] + + async def test_get_chips_history_cached_with_unknown_gameweek(self, loop, mocker, user): + user._history = {"chips": [{"event": 1}, {"event": 2}, {"event": 3}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + chips_history = await user.get_chips_history() + assert chips_history is user._history["chips"] + mocked_fetch.assert_not_called() + + async def test_get_chips_history_non_cached_with_unknown_gameweek(self, loop, mocker, user): + data = {"chips": [{"event": 1}, {"event": 2}, {"event": 3}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + chips_history = await user.get_chips_history() + assert chips_history is mocked_fetch.return_value["chips"] + mocked_fetch.assert_called_once() - async def test_chips_history(self, loop, user): - chips = await user.get_chips_history() - assert isinstance(chips, list) + async def test_get_chips_history_cached_with_known_gameweek(self, loop, mocker, user): + user._history = {"chips": [{"event": 1}, {"event": 2}, {"event": 3}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + history = await user.get_chips_history(1) + assert history is user._history["chips"][0] + mocked_fetch.assert_not_called() + + async def test_get_chips_history_non_cached_with_known_gameweek(self, loop, mocker, user): + data = {"chips": [{"event": 1}, {"event": 2}, {"event": 3}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + chips_history = await user.get_chips_history(1) + assert chips_history is mocked_fetch.return_value["chips"][0] + mocked_fetch.assert_called_once() async def test_leagues(self, loop, user): leagues = user.leagues assert isinstance(leagues, dict) - async def test_picks(self, loop, user): + async def test_get_picks_cached_with_unknown_gameweek(self, loop, mocker, user): + picks_list = [{"element": 282}, {"element": 280}, {"element": 284}, {"element": 286}] + user._picks = [{"event": {"id": 1}, "picks": picks_list[:2]}, + {"event": {"id": 2}, "picks": picks_list[2:]}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + picks = await user.get_picks() + assert picks == picks_list + mocked_fetch.assert_not_called() + + async def test_get_picks_non_cached_with_unknown_gameweek(self, loop, mocker, user): + picks_list = [{"element": 282}, {"element": 280}] + data = {"event": {"id": 1}, "picks": picks_list} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) picks = await user.get_picks() assert isinstance(picks, list) - assert len(picks) == user.current_event + assert len(picks) == user.current_event * len(picks_list) + assert mocked_fetch.call_count == user.current_event + async def test_get_picks_cached_with_known_gameweek(self, loop, mocker, user): + picks_list = [{"element": 282}, {"element": 280}, {"element": 284}, {"element": 286}] + user._picks = [{"event": {"id": 1}, "picks": picks_list[:2]}, + {"event": {"id": 2}, "picks": picks_list[2:]}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) picks = await user.get_picks(1) + assert picks == picks_list[:2] + mocked_fetch.assert_not_called() + + async def test_get_picks_non_cached_with_known_gameweek(self, loop, mocker, user): + picks_list = [{"element": 282}, {"element": 280}] + data = {"event": {"id": 1}, "picks": picks_list} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + picks = await user.get_picks(1) + assert isinstance(picks, list) + assert picks == picks_list + assert mocked_fetch.call_count == user.current_event + + async def test_get_active_chips_cached_with_unknown_gameweek(self, loop, mocker, user): + user._picks = [{"event": {"id": 1}, "active_chip": "chip one"}, + {"event": {"id": 2}, "active_chip": "chip two"}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + picks = await user.get_active_chips() + assert picks == ["chip one", "chip two"] + mocked_fetch.assert_not_called() + + async def test_get_active_chips_non_cached_with_unknown_gameweek(self, loop, mocker, user): + data = {"event": {"id": 1}, "active_chip": "chip one"} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + picks = await user.get_active_chips() assert isinstance(picks, list) + assert len(picks) == user.current_event + assert mocked_fetch.call_count == user.current_event - async def test_active_chips(self, loop, user): - active_chips = await user.get_active_chips() - assert isinstance(active_chips, list) - assert len(active_chips) == user.current_event + async def test_get_active_chips_cached_with_known_gameweek(self, loop, mocker, user): + user._picks = [{"event": {"id": 1}, "active_chip": "chip one"}, + {"event": {"id": 2}, "active_chip": "chip two"}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + picks = await user.get_active_chips(1) + assert picks == ["chip one"] + mocked_fetch.assert_not_called() - active_chips = await user.get_active_chips(1) - assert isinstance(active_chips, list) + async def test_get_active_chips_non_cached_with_known_gameweek(self, loop, mocker, user): + data = {"event": {"id": 1}, "active_chip": "chip one"} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + picks = await user.get_active_chips(1) + assert picks == ["chip one"] + assert mocked_fetch.call_count == user.current_event - async def test_automatic_substitutions(self, loop, user): - automatic_substitutions = await user.get_automatic_substitutions() - assert isinstance(automatic_substitutions, list) - assert len(automatic_substitutions) == user.current_event + async def test_get_automatic_substitutions_cached_with_unknown_gameweek(self, loop, mocker, user): + user._picks = [{"event": {"id": 1}, "automatic_subs": [{"id": 6812275}]}, + {"event": {"id": 2}, "automatic_subs": [{"id": 6800000}]}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + picks = await user.get_automatic_substitutions() + assert picks == [{"id": 6812275}, {"id": 6800000}] + mocked_fetch.assert_not_called() - automatic_substitutions = await user.get_automatic_substitutions(1) - assert isinstance(automatic_substitutions, list) + async def test_get_automatic_substitutions_non_cached_with_unknown_gameweek(self, loop, mocker, user): + data = {"event": {"id": 1}, "automatic_subs": [{"id": 6812275}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + picks = await user.get_automatic_substitutions() + assert isinstance(picks, list) + assert len(picks) == user.current_event + assert mocked_fetch.call_count == user.current_event - async def test_team(self, loop, user): + async def test_get_automatic_substitutions_cached_with_known_gameweek(self, loop, mocker, user): + user._picks = [{"event": {"id": 1}, "automatic_subs": [{"id": 6812275}]}, + {"event": {"id": 2}, "automatic_subs": [{"id": 6800000}]}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + picks = await user.get_automatic_substitutions(1) + assert picks == [{"id": 6812275}] + mocked_fetch.assert_not_called() + + async def test_get_automatic_substitutions_non_cached_with_known_gameweek(self, loop, mocker, user): + data = {"event": {"id": 1}, "automatic_subs": [{"id": 6812275}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + picks = await user.get_automatic_substitutions(1) + assert picks == [{"id": 6812275}] + assert mocked_fetch.call_count == user.current_event + + async def test_get_team_not_authenticated(self, loop, mocker, user): + mocked_logged_in = mocker.patch("fpl.models.user.logged_in", + return_value=False) + with pytest.raises(Exception): + await user.get_team() + mocked_logged_in.assert_called_once() + + async def test_get_team_authenticated_not_matching_credentials_with_user_id(self, loop, mocker, user): + mocked_logged_in = mocker.patch("fpl.models.user.logged_in", + return_value=True) + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value={"details": "You cannot view this entry"}, + new_callable=AsyncMock) + with pytest.raises(ValueError): + await user.get_team() + mocked_logged_in.assert_called_once() + mocked_fetch.assert_called_once() + + async def test_get_team_authenticated_matching_credentials_with_user_id(self, loop, mocker, user): + mocked_logged_in = mocker.patch("fpl.models.user.logged_in", + return_value=True) + data = {"picks": [{"element": 1}, {"element": 2}]} + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value=data, + new_callable=AsyncMock) team = await user.get_team() assert isinstance(team, list) + mocked_logged_in.assert_called_once() + mocked_fetch.assert_called_once() - async def test_transfers(self, loop, user): + async def test_get_transfers_cached_with_unknown_gameweek(self, loop, mocker, user): + transfers_data = [{"id": 6812275, "event": 2}, {"id": 6800000, "event": 3}] + user._transfers = {"event": {"id": 1}, "history": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) transfers = await user.get_transfers() - assert isinstance(transfers, list) + assert transfers == transfers_data + mocked_fetch.assert_not_called() + + async def test_get_transfers_non_cached_with_unknown_gameweek(self, loop, mocker, user): + transfers_data = [{"id": 6812275, "event": 2}, {"id": 6800000, "event": 3}] + data = {"event": {"id": 1}, "history": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + transfers = await user.get_transfers() + assert transfers == transfers_data + mocked_fetch.assert_called_once() + + async def test_get_transfers_cached_with_known_gameweek(self, loop, mocker, user): + transfers_data = [{"id": 6812275, "event": 2}, {"id": 6800000, "event": 3}] + user._transfers = {"event": {"id": 1}, "history": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + transfers = await user.get_transfers(2) + assert transfers == [transfers_data[0]] + mocked_fetch.assert_not_called() + + async def test_get_transfers_non_cached_with_known_gameweek(self, loop, mocker, user): + transfers_data = [{"id": 6812275, "event": 2}, {"id": 6800000, "event": 3}] + data = {"event": {"id": 1}, "history": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + transfers = await user.get_transfers(2) + assert transfers == [transfers_data[0]] + mocked_fetch.assert_called_once() + + async def test_get_wildcards_cached(self, loop, mocker, user): + transfers_data = [{"event": 2}, {"event": 3}] + user._transfers = {"event": {"id": 1}, "wildcards": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value={}, new_callable=AsyncMock) + transfers = await user.get_wildcards() + assert transfers == transfers_data + mocked_fetch.assert_not_called() - transfers = await user.get_transfers(1) - assert isinstance(transfers, list) + async def test_get_wildcards_non_cached(self, loop, mocker, user): + transfers_data = [{"event": 2}, {"event": 3}] + data = {"event": {"id": 1}, "wildcards": transfers_data} + mocked_fetch = mocker.patch("fpl.models.user.fetch", return_value=data, new_callable=AsyncMock) + transfers = await user.get_wildcards() + assert transfers == transfers_data + mocked_fetch.assert_called_once() - async def test_wildcards(self, loop, user): - wildcards = await user.get_wildcards() - assert isinstance(wildcards, list) + async def test_get_watchlist_not_authenticated(self, loop, mocker, user): + mocked_logged_in = mocker.patch("fpl.models.user.logged_in", + return_value=False) + with pytest.raises(Exception): + await user.get_watchlist() + mocked_logged_in.assert_called_once() - async def test_watchlist(self, loop, user): + async def test_get_watchlist_authenticated(self, loop, mocker, user): + mocked_logged_in = mocker.patch("fpl.models.user.logged_in", + return_value=True) + data = [{"element": 1}, {"element": 2}] + mocked_fetch = mocker.patch("fpl.models.user.fetch", + return_value=data, + new_callable=AsyncMock) watchlist = await user.get_watchlist() - assert isinstance(watchlist, list) + assert watchlist == data + mocked_logged_in.assert_called_once() + mocked_fetch.assert_called_once()