diff --git a/shvatka/api/models/responses.py b/shvatka/api/models/responses.py index 091fd639..dacd2c70 100644 --- a/shvatka/api/models/responses.py +++ b/shvatka/api/models/responses.py @@ -3,7 +3,8 @@ from datetime import datetime from typing import Sequence, Generic -from shvatka.core.models import dto +from shvatka.core.games.dto import CurrentHints +from shvatka.core.models import dto, enums from shvatka.core.models.dto import scn from shvatka.core.models.enums import GameStatus @@ -114,3 +115,47 @@ def from_core(cls, core: dto.FullGame | None = None): start_at=core.start_at, levels=[Level.from_core(level) for level in core.levels], ) + + +@dataclass(frozen=True) +class KeyTime: + text: str + type_: enums.KeyType + is_duplicate: bool + at: datetime + level_number: int + player: Player + team: Team + + @classmethod + def from_core(cls, core: dto.KeyTime | None): + if core is None: + return None + return cls( + text=core.text, + type_=core.type_, + is_duplicate=core.is_duplicate, + at=core.at, + level_number=core.level_number, + player=Player.from_core(core.player), + team=Team.from_core(core.team), + ) + + +@dataclass +class CurrentHintResponse: + hints: list[scn.TimeHint] + typed_keys: list[KeyTime] + level_number: int + started_at: datetime + + @classmethod + def from_core(cls, core: CurrentHints): + if core is None: + return None + return cls( + hints=core.hints, + typed_keys=[KeyTime.from_core(kt) for kt in core.typed_keys], + level_number=core.level_number, + started_at=core.started_at, + ) diff --git a/shvatka/api/routes/game.py b/shvatka/api/routes/game.py index 9133092e..307aa98e 100644 --- a/shvatka/api/routes/game.py +++ b/shvatka/api/routes/game.py @@ -9,7 +9,7 @@ from shvatka.api.models import responses from shvatka.api.utils.error_converter import to_http_error -from shvatka.core.games.interactors import FileReader +from shvatka.core.games.interactors import GameFileReaderInteractor, GamePlayReaderInteractor from shvatka.core.models import dto from shvatka.core.services.game import ( get_authors_games, @@ -61,7 +61,7 @@ async def get_game_card( @inject async def get_game_file( user: Annotated[dto.User, FromDishka()], - file_reader: Annotated[FileReader, FromDishka()], + file_reader: Annotated[GameFileReaderInteractor, FromDishka()], id_: Annotated[int, Path(alias="id")], guid: Annotated[str, Path(alias="guid")], ) -> StreamingResponse: @@ -73,11 +73,20 @@ async def get_game_file( raise to_http_error(e, HTTP_403_FORBIDDEN) from e +@inject +async def get_running_game_hints( + user: Annotated[dto.User, FromDishka()], + interactor: Annotated[GamePlayReaderInteractor, FromDishka()], +) -> responses.CurrentHintResponse: + return responses.CurrentHintResponse.from_core(await interactor(user)) + + def setup() -> APIRouter: router = APIRouter(prefix="/games") router.add_api_route("", get_all_games, methods=["GET"]) router.add_api_route("/my", get_my_games_list, methods=["GET"]) router.add_api_route("/active", get_active_game, methods=["GET"]) + router.add_api_route("/running/hints", get_running_game_hints, methods=["GET"]) router.add_api_route("/{id}", get_game_card, methods=["GET"]) router.add_api_route("/{id}/files/{guid}", get_game_file, methods=["GET"]) return router diff --git a/shvatka/core/games/adapters.py b/shvatka/core/games/adapters.py new file mode 100644 index 00000000..ba26da3d --- /dev/null +++ b/shvatka/core/games/adapters.py @@ -0,0 +1,26 @@ +from typing import Protocol + +from shvatka.core.interfaces.dal.file_info import FileInfoGetter +from shvatka.core.interfaces.dal.game import GameByIdGetter, ActiveGameFinder +from shvatka.core.interfaces.dal.key_log import GameTeamKeyGetter +from shvatka.core.interfaces.dal.level import LevelByGameAndNumberGetter +from shvatka.core.interfaces.dal.level_times import LevelByTeamGetter +from shvatka.core.interfaces.dal.player import PlayerByUserGetter, TeamByPlayerGetter +from shvatka.core.interfaces.dal.waiver import WaiverChecker + + +class GameFileReader(FileInfoGetter, GameByIdGetter, PlayerByUserGetter, Protocol): + pass + + +class GamePlayReader( + ActiveGameFinder, + PlayerByUserGetter, + TeamByPlayerGetter, + WaiverChecker, + LevelByTeamGetter, + LevelByGameAndNumberGetter, + GameTeamKeyGetter, + Protocol, +): + pass diff --git a/shvatka/core/games/dto.py b/shvatka/core/games/dto.py new file mode 100644 index 00000000..fd3a2023 --- /dev/null +++ b/shvatka/core/games/dto.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from datetime import datetime + +from shvatka.core.models import dto +from shvatka.core.models.dto import scn + + +@dataclass +class CurrentHints: + hints: list[scn.TimeHint] + typed_keys: list[dto.KeyTime] + level_number: int + started_at: datetime diff --git a/shvatka/core/games/interactors.py b/shvatka/core/games/interactors.py index 3d275c1f..59a3030e 100644 --- a/shvatka/core/games/interactors.py +++ b/shvatka/core/games/interactors.py @@ -1,15 +1,18 @@ +from datetime import datetime from typing import BinaryIO +from shvatka.core.games.dto import CurrentHints from shvatka.core.interfaces.clients.file_storage import FileGateway -from shvatka.core.interfaces.dal.complex import GameFileLoader +from shvatka.core.games.adapters import GameFileReader, GamePlayReader from shvatka.core.models import dto from shvatka.core.rules.game import check_can_read from shvatka.core.services.scenario.files import check_file_meta_can_read from shvatka.core.utils import exceptions +from shvatka.core.utils.datetime_utils import tz_utc -class FileReader: - def __init__(self, dao: GameFileLoader, file_gateway: FileGateway): +class GameFileReaderInteractor: + def __init__(self, dao: GameFileReader, file_gateway: FileGateway): self.file_gateway = file_gateway self.dao = dao @@ -30,3 +33,34 @@ async def __call__(self, guid: str, game_id: int, user: dto.User) -> BinaryIO: meta = await self.dao.get_by_guid(guid) check_file_meta_can_read(player, meta, game) return await self.file_gateway.get(meta) + + +class GamePlayReaderInteractor: + def __init__(self, dao: GamePlayReader): + self.dao = dao + + async def __call__(self, user: dto.User) -> CurrentHints: + player = await self.dao.get_by_user(user) + team = await self.dao.get_team(player) + if not team: + raise exceptions.PlayerNotInTeam( + player=player, + user=user, + ) + game = await self.dao.get_active_game() + if game is None: + raise exceptions.HaveNotActiveGame(game=game, user=user) + if not await self.dao.check_waiver(player, team, game): + raise exceptions.WaiverError( + team=team, game=game, player=player, text="игрок не заявлен на игру, но ввёл ключ" + ) + level_time = await self.dao.get_current_level_time(team, game) + level = await self.dao.get_level_by_game_and_number(game, level_time.level_number) + hints = level.get_hints_for_timedelta(datetime.now(tz=tz_utc) - level_time.start_at) + keys = await self.dao.get_team_typed_keys(game, team, level_time.level_number) + return CurrentHints( + hints=hints, + typed_keys=keys, + level_number=level_time.level_number, + started_at=level_time.start_at, + ) diff --git a/shvatka/core/interfaces/dal/complex.py b/shvatka/core/interfaces/dal/complex.py index c154395f..0a0ac4f4 100644 --- a/shvatka/core/interfaces/dal/complex.py +++ b/shvatka/core/interfaces/dal/complex.py @@ -6,12 +6,11 @@ MaxGameNumberGetter, GameNumberUpdater, GameStatusCompleter, - GameByIdGetter, ) from shvatka.core.interfaces.dal.key_log import TeamKeysMerger, GameKeyGetter from shvatka.core.interfaces.dal.level_times import TeamLevelsMerger, LevelTimesGetter from shvatka.core.interfaces.dal.organizer import OrgByPlayerGetter -from shvatka.core.interfaces.dal.player import TeamPlayersMerger, PlayerByUserGetter +from shvatka.core.interfaces.dal.player import TeamPlayersMerger from shvatka.core.interfaces.dal.team import ForumTeamMerger, TeamRemover from shvatka.core.interfaces.dal.waiver import WaiverMerger, GameWaiversGetter from shvatka.core.models import dto @@ -48,7 +47,3 @@ class GameCompleter( class GamePackager(GameKeyGetter, LevelTimesGetter, GameWaiversGetter, FileInfoGetter, Protocol): async def get_full(self, id_: int) -> dto.FullGame: raise NotImplementedError - - -class GameFileLoader(FileInfoGetter, GameByIdGetter, PlayerByUserGetter, Protocol): - pass diff --git a/shvatka/core/interfaces/dal/game_play.py b/shvatka/core/interfaces/dal/game_play.py index 996c37de..4b4948fa 100644 --- a/shvatka/core/interfaces/dal/game_play.py +++ b/shvatka/core/interfaces/dal/game_play.py @@ -1,7 +1,9 @@ from typing import Iterable, Protocol from shvatka.core.interfaces.dal.base import Committer +from shvatka.core.interfaces.dal.level_times import LevelByTeamGetter from shvatka.core.interfaces.dal.organizer import GameOrgsGetter +from shvatka.core.interfaces.dal.waiver import WaiverChecker from shvatka.core.models import dto, enums @@ -16,10 +18,7 @@ async def get_poll_msg(self, team: dto.Team, game: dto.Game) -> int | None: raise NotImplementedError -class GamePlayerDao(Committer, GameOrgsGetter, Protocol): - async def check_waiver(self, player: dto.Player, team: dto.Team, game: dto.Game) -> bool: - raise NotImplementedError - +class GamePlayerDao(Committer, WaiverChecker, GameOrgsGetter, LevelByTeamGetter, Protocol): async def is_key_duplicate(self, level: dto.Level, team: dto.Team, key: str) -> bool: raise NotImplementedError @@ -60,6 +59,3 @@ async def level_up(self, team: dto.Team, level: dto.Level, game: dto.Game) -> No async def finish(self, game: dto.Game) -> None: raise NotImplementedError - - async def get_current_level_time(self, team: dto.Team, game: dto.Game) -> dto.LevelTime: - raise NotImplementedError diff --git a/shvatka/core/interfaces/dal/key_log.py b/shvatka/core/interfaces/dal/key_log.py index e0f8eb56..4af70c7c 100644 --- a/shvatka/core/interfaces/dal/key_log.py +++ b/shvatka/core/interfaces/dal/key_log.py @@ -8,6 +8,13 @@ async def get_typed_keys_grouped(self, game: dto.Game) -> dict[dto.Team, list[dt raise NotImplementedError +class GameTeamKeyGetter(Protocol): + async def get_team_typed_keys( + self, game: dto.Game, team: dto.Team, level_number: int + ) -> list[dto.KeyTime]: + raise NotImplementedError + + class TeamKeysMerger(Protocol): async def replace_team_keys(self, primary: dto.Team, secondary: dto.Team): raise NotImplementedError diff --git a/shvatka/core/interfaces/dal/level.py b/shvatka/core/interfaces/dal/level.py index 106d2780..71cabc37 100644 --- a/shvatka/core/interfaces/dal/level.py +++ b/shvatka/core/interfaces/dal/level.py @@ -19,6 +19,11 @@ async def unlink_all(self, game: dto.Game) -> None: raise NotImplementedError +class LevelByGameAndNumberGetter(Protocol): + async def get_level_by_game_and_number(self, game: dto.Game, number: int) -> dto.Level: + raise NotImplementedError + + class MyLevelsGetter(Protocol): async def get_all_my(self, author: dto.Player) -> list[dto.Level]: raise NotImplementedError diff --git a/shvatka/core/interfaces/dal/level_times.py b/shvatka/core/interfaces/dal/level_times.py index 2d865d2e..17b179e2 100644 --- a/shvatka/core/interfaces/dal/level_times.py +++ b/shvatka/core/interfaces/dal/level_times.py @@ -30,6 +30,11 @@ async def get_game_level_times_by_teams( raise NotImplementedError +class LevelByTeamGetter(Protocol): + async def get_current_level_time(self, team: dto.Team, game: dto.Game) -> dto.LevelTime: + raise NotImplementedError + + class TeamLevelsMerger(Protocol): async def replace_team_levels(self, primary: dto.Team, secondary: dto.Team): raise NotImplementedError diff --git a/shvatka/core/interfaces/dal/player.py b/shvatka/core/interfaces/dal/player.py index 000065f6..421ac4aa 100644 --- a/shvatka/core/interfaces/dal/player.py +++ b/shvatka/core/interfaces/dal/player.py @@ -36,11 +36,13 @@ async def get_team_player(self, player: dto.Player) -> dto.TeamPlayer: raise NotImplementedError -class PlayerTeamChecker(TeamPlayerGetter, Protocol): - async def have_team(self, player: dto.Player) -> bool: +class TeamByPlayerGetter(Protocol): + async def get_team(self, player: dto.Player) -> dto.Team | None: raise NotImplementedError - async def get_team(self, player: dto.Player) -> dto.Team | None: + +class PlayerTeamChecker(TeamPlayerGetter, TeamByPlayerGetter, Protocol): + async def have_team(self, player: dto.Player) -> bool: raise NotImplementedError diff --git a/shvatka/core/interfaces/dal/waiver.py b/shvatka/core/interfaces/dal/waiver.py index 91591bf9..65d4a418 100644 --- a/shvatka/core/interfaces/dal/waiver.py +++ b/shvatka/core/interfaces/dal/waiver.py @@ -54,3 +54,8 @@ async def get_all_by_game(self, game: dto.Game) -> list[dto.Waiver]: class WaiverMerger(Protocol): async def replace_team_waiver(self, primary: dto.Team, secondary: dto.Team): raise NotImplementedError + + +class WaiverChecker(Protocol): + async def check_waiver(self, player: dto.Player, team: dto.Team, game: dto.Game) -> bool: + raise NotImplementedError diff --git a/shvatka/core/models/dto/level.py b/shvatka/core/models/dto/level.py index 09713521..f1b5035f 100644 --- a/shvatka/core/models/dto/level.py +++ b/shvatka/core/models/dto/level.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import timedelta from .player import Player from .scn.level import LevelScenario, BonusKey @@ -37,3 +38,6 @@ def get_guids(self) -> list[str]: @property def hints_count(self) -> int: return self.scenario.hints_count + + def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]: + return self.scenario.get_hints_for_timedelta(delta) diff --git a/shvatka/core/models/dto/scn/level.py b/shvatka/core/models/dto/scn/level.py index 0155a880..dc2d27eb 100644 --- a/shvatka/core/models/dto/scn/level.py +++ b/shvatka/core/models/dto/scn/level.py @@ -1,5 +1,6 @@ import typing from dataclasses import dataclass, field +from datetime import timedelta from .time_hint import TimeHint @@ -48,3 +49,7 @@ def get_guids(self) -> list[str]: @property def hints_count(self) -> int: return sum(time_hint.hints_count for time_hint in self.time_hints) + + def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]: + minutes = delta.total_seconds() // 60 + return [th for th in self.time_hints if th.time <= minutes] diff --git a/shvatka/core/services/game_play.py b/shvatka/core/services/game_play.py index 4ec703d5..50acd56f 100644 --- a/shvatka/core/services/game_play.py +++ b/shvatka/core/services/game_play.py @@ -249,15 +249,6 @@ async def send_hint( await scheduler.plain_hint(level, team, next_hint_number, next_hint_time) -async def get_available_hints( - game: dto.Game, team: dto.Team, dao: GamePlayerDao -) -> list[scn.TimeHint]: - level_time = await dao.get_current_level_time(team=team, game=game) - level = await dao.get_current_level(team=team, game=game) - from_start_level_minutes = (datetime.now(tz=tz_utc) - level_time.start_at).seconds // 60 - return list(filter(lambda th: th.time <= from_start_level_minutes, level.scenario.time_hints)) - - async def schedule_first_hint( scheduler: Scheduler, team: dto.Team, diff --git a/shvatka/infrastructure/db/dao/complex/game.py b/shvatka/infrastructure/db/dao/complex/game.py index 250f7d76..2467eaf9 100644 --- a/shvatka/infrastructure/db/dao/complex/game.py +++ b/shvatka/infrastructure/db/dao/complex/game.py @@ -2,7 +2,8 @@ from dataclasses import dataclass from typing import Iterable -from shvatka.core.interfaces.dal.complex import GamePackager, GameFileLoader +from shvatka.core.interfaces.dal.complex import GamePackager +from shvatka.core.games.adapters import GameFileReader, GamePlayReader from shvatka.core.interfaces.dal.game import GameUpserter, GameCreator from shvatka.core.models import dto from shvatka.core.models.dto import scn @@ -101,7 +102,7 @@ async def get_by_guid(self, guid: str) -> scn.VerifiableFileMeta: return await self.dao.file_info.get_by_guid(guid) -class GameFilesGetterImpl(GameFileLoader): +class GameFilesGetterImpl(GameFileReader): def __init__(self, dao: "HolderDao"): self.dao = dao @@ -116,3 +117,31 @@ async def get_full(self, id_: int) -> dto.FullGame: async def get_by_user(self, user: dto.User) -> dto.Player: return await self.dao.player.get_by_user(user) + + +class GamePlayReaderImpl(GamePlayReader): + def __init__(self, dao: "HolderDao"): + self.dao = dao + + async def get_active_game(self) -> dto.Game | None: + return await self.dao.game.get_active_game() + + async def get_by_user(self, user: dto.User) -> dto.Player: + return await self.dao.player.get_by_user(user) + + async def get_team(self, player: dto.Player) -> dto.Team | None: + return await self.dao.team_player.get_team(player) + + async def check_waiver(self, player: dto.Player, team: dto.Team, game: dto.Game) -> bool: + return await self.dao.waiver.check_waiver(player, team, game) + + async def get_current_level_time(self, team: dto.Team, game: dto.Game) -> dto.LevelTime: + return await self.dao.level_time.get_current_level_time(team, game) + + async def get_level_by_game_and_number(self, game: dto.Game, number: int) -> dto.Level: + return await self.dao.level.get_by_number(game, number) + + async def get_team_typed_keys( + self, game: dto.Game, team: dto.Team, level_number: int + ) -> list[dto.KeyTime]: + return await self.dao.key_time.get_team_typed_keys(game, team, level_number) diff --git a/shvatka/infrastructure/db/dao/rdb/log_keys.py b/shvatka/infrastructure/db/dao/rdb/log_keys.py index f030d57e..95444b2d 100644 --- a/shvatka/infrastructure/db/dao/rdb/log_keys.py +++ b/shvatka/infrastructure/db/dao/rdb/log_keys.py @@ -31,6 +31,30 @@ async def get_correct_typed_keys( ) return {key.key_text for key in result.all()} + async def get_team_typed_keys( + self, + game: dto.Game, + team: dto.Team, + level_number: int, + ) -> list[dto.KeyTime]: + result: ScalarResult[models.KeyTime] = await self.session.scalars( + select(models.KeyTime) + .options( + joinedload(models.KeyTime.player).options( + joinedload(models.Player.user), joinedload(models.Player.forum_user) + ), + ) + .where( + models.KeyTime.game_id == game.id, + models.KeyTime.level_number == level_number, + models.KeyTime.team_id == team.id, + ) + ) + return [ + key.to_dto(player=key.player.to_dto_user_prefetched(), team=team) + for key in result.all() + ] + async def is_duplicate(self, level: dto.Level, team: dto.Team, key: str) -> bool: result: ScalarResult[int] = await self.session.scalars( select(self.model.id) diff --git a/shvatka/infrastructure/di/__init__.py b/shvatka/infrastructure/di/__init__.py index 7360f1de..5c7ec7a9 100644 --- a/shvatka/infrastructure/di/__init__.py +++ b/shvatka/infrastructure/di/__init__.py @@ -2,7 +2,7 @@ from shvatka.infrastructure.di.config import ConfigProvider, DbConfigProvider from shvatka.infrastructure.di.db import DbProvider, RedisProvider from shvatka.infrastructure.di.files import FileClientProvider -from shvatka.infrastructure.di.interactors import DAOProvider, InteractorProvider +from shvatka.infrastructure.di.interactors import GamePlayProvider def get_providers(paths_env): @@ -13,6 +13,5 @@ def get_providers(paths_env): RedisProvider(), FileClientProvider(), BotProvider(), - DAOProvider(), - InteractorProvider(), + GamePlayProvider(), ] diff --git a/shvatka/infrastructure/di/interactors.py b/shvatka/infrastructure/di/interactors.py index 606df6ce..f0256af3 100644 --- a/shvatka/infrastructure/di/interactors.py +++ b/shvatka/infrastructure/di/interactors.py @@ -1,19 +1,22 @@ from dishka import Provider, Scope, provide -from shvatka.core.games.interactors import FileReader -from shvatka.core.interfaces.dal.complex import GameFileLoader -from shvatka.infrastructure.db.dao.complex.game import GameFilesGetterImpl +from shvatka.core.games.interactors import GameFileReaderInteractor, GamePlayReaderInteractor +from shvatka.core.games.adapters import GameFileReader, GamePlayReader +from shvatka.infrastructure.db.dao.complex.game import GameFilesGetterImpl, GamePlayReaderImpl from shvatka.infrastructure.db.dao.holder import HolderDao -class DAOProvider(Provider): +class GamePlayProvider(Provider): scope = Scope.REQUEST @provide - def get_game_files(self, dao: HolderDao) -> GameFileLoader: + def get_game_files(self, dao: HolderDao) -> GameFileReader: return GameFilesGetterImpl(dao) + file_reader = provide(GameFileReaderInteractor) -class InteractorProvider(Provider): - scope = Scope.REQUEST - file_reader = provide(FileReader) + @provide + def game_play_reader(self, dao: HolderDao) -> GamePlayReader: + return GamePlayReaderImpl(dao) + + game_play_reader_interactor = provide(GamePlayReaderInteractor) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 0f043120..537269de 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -25,8 +25,7 @@ DbProvider, RedisProvider, FileClientProvider, - InteractorProvider, - DAOProvider, + GamePlayProvider, ) from shvatka.tgbot.main_factory import DpProvider, LockProvider from shvatka.tgbot.username_resolver.user_getter import UserGetter @@ -71,8 +70,7 @@ async def dishka(): LockProvider(), DCFProvider(), TelegraphProvider(), - InteractorProvider(), - DAOProvider(), + GamePlayProvider(), mock_provider, ) yield container diff --git a/tests/integration/test_game_play.py b/tests/integration/test_game_play.py index 3fde7af7..7eefb2bc 100644 --- a/tests/integration/test_game_play.py +++ b/tests/integration/test_game_play.py @@ -1,19 +1,20 @@ from datetime import datetime, timedelta import pytest -from dataclass_factory import Factory +from dishka import AsyncContainer -from shvatka.core.interfaces.clients.file_storage import FileStorage +from shvatka.core.games.interactors import GamePlayReaderInteractor from shvatka.core.models import dto, enums from shvatka.core.models.enums import GameStatus from shvatka.core.models.enums.played import Played from shvatka.core.services.game import start_waivers -from shvatka.core.services.game_play import start_game, send_hint, check_key, get_available_hints +from shvatka.core.services.game_play import start_game, send_hint, check_key from shvatka.core.services.game_stat import get_typed_keys from shvatka.core.services.key import KeyProcessor from shvatka.core.services.organizers import get_orgs from shvatka.core.services.player import join_team from shvatka.core.services.waiver import add_vote, approve_waivers +from shvatka.core.utils import exceptions from shvatka.core.utils.datetime_utils import tz_utc from shvatka.core.utils.key_checker_lock import KeyCheckerFactory from shvatka.core.views.game import ( @@ -174,12 +175,11 @@ async def test_game_play( @pytest.mark.asyncio async def test_get_current_hints( game: dto.FullGame, + dishka_request: AsyncContainer, dao: HolderDao, - dcf: Factory, - locker: KeyCheckerFactory, - file_storage: FileStorage, author: dto.Player, harry: dto.Player, + ron: dto.Player, hermione: dto.Player, gryffindor: dto.Team, ): @@ -197,5 +197,14 @@ async def test_get_current_hints( ) dao.level_time._save(level_time) await dao.commit() - actual_hints = await get_available_hints(game, gryffindor, dao.game_player) - assert len(actual_hints) == 2 + interactor = await dishka_request.get(GamePlayReaderInteractor) + hints = await interactor(hermione._user) + hints_harry = await interactor(harry._user) + assert len(hints.hints) == 2 + assert len(hints_harry.hints) == 2 + assert hints_harry.hints == hints.hints + with pytest.raises(exceptions.PlayerNotInTeam): + await interactor(ron._user) + await join_team(ron, gryffindor, harry, dao.team_player) + with pytest.raises(exceptions.WaiverError): + await interactor(ron._user)