async def test_list_all_without_closed_option(): # Given list_games_message = "message listing all games, open and closed" message_provider = MagicMock(MessageProvider) message_provider.channel_manage_list_all_games.return_value = list_games_message open_game = Game(closed=False) closed_game = Game(closed=True) games_repository = FakeGamesRepository([open_game, closed_game]) route = ListGamesRoute( games_repository=games_repository, message_provider=message_provider, ) # When event = _make_event(options={}) response = await route.call(event) # Then message_provider.channel_manage_list_all_games.assert_called() used_games = message_provider.channel_manage_list_all_games.call_args.args[ 0] assert closed_game in used_games assert open_game in used_games assert response.is_ephemeral assert response.content == list_games_message
async def test_list_all_open_games(): # Given guild_id = 100 # We have one open and one closed game open_game = Game(closed=False) closed_game = Game(closed=True) games_repository = FakeGamesRepository([open_game, closed_game]) # And we have a mock message provider list_games_message = "message listing all games, open and closed" message_provider = MagicMock(MessageProvider) message_provider.channel_manage_list_open_games.return_value = list_games_message route = ListGamesRoute( games_repository=games_repository, message_provider=message_provider, ) # When we list only the open games event = _make_event(guild_id=guild_id, options={'closed': False}) response = await route.call(event) # Then: a message is sent based on only the open games message_provider.channel_manage_list_open_games.assert_called() used_games = message_provider.channel_manage_list_open_games.call_args.args[ 0] assert closed_game not in used_games assert open_game in used_games assert response.is_ephemeral assert response.content == list_games_message
async def test_close_already_closed_game(): # Given guild_id = 1007 game_id = 'game-id-1' # We have a closed game game = Game(game_id=game_id, guild_id=guild_id, closed=True) games_repository = FakeGamesRepository([game]) event = _make_event(guild_id=guild_id, options={ 'game-id': game.game_id, }) game_already_closed_message = "Game was already closed closed." message_provider = MagicMock(MessageProvider) message_provider.game_closed.return_value = game_already_closed_message route = CloseGameRoute( games_repository=games_repository, discord_messaging=FakeDiscordMessaging(), message_provider=message_provider, ) # When we close it again response = await route.call(event) # Then it should just still be closed saved_game = games_repository.get(guild_id, game.game_id) assert saved_game.closed assert response.is_ephemeral assert response.content == game_already_closed_message
async def test_guess_replies_with_ephemeral_message(): # Given guild_id = 1005 user_id = 13000 game_id = 'game-id' response_message = "message with new guess" message_provider = MagicMock(MessageProvider, autospec=True) message_provider.guess_added.return_value = response_message game = Game(guild_id=guild_id, game_id=game_id) fake_games_repository = FakeGamesRepository([game]) guess_route = _route( games_repository=fake_games_repository, message_provider=message_provider, ) # When event = _create_guess_event(guild_id, game.game_id, user_id, 'nickname') response = await guess_route.call(event) # Then assert response.is_ephemeral assert response.content == response_message
async def test_guess_updates_channel_messages(): # Given guild_id = 1001 user_id = 12000 post_embed = discord.Embed() message_provider = MagicMock(MessageProvider) message_provider.game_post_embed.return_value = post_embed game = Game( guild_id=guild_id, game_id="game-1", ) games_repository = FakeGamesRepository([game]) game_post_manager = MagicMock(GamePostManager) guess_route = _route( games_repository=games_repository, message_provider=message_provider, game_post_manager=game_post_manager, ) # When event = _create_guess_event(guild_id, game.game_id, user_id, 'nickname') await guess_route.call(event) # Then game_post_manager.update.assert_called_with(game)
async def test_guess_non_numerical_on_numeric_game(): # Given guild_id = 1005 user_id = 13000 game_id = 'game-id' response_message = "Guess must be a number" message_provider = MagicMock(MessageProvider, autospec=True) message_provider.invalid_guess.return_value = response_message # And we have a game with a min and a max game = Game(guild_id=guild_id, game_id=game_id, min_guess=1, max_guess=100) games_repository = FakeGamesRepository([game]) guess_route = _route( games_repository=games_repository, message_provider=message_provider, ) # When event = _create_guess_event(guild_id, game.game_id, user_id, 'nickname') response = await guess_route.call(event) # Then assert response.is_ephemeral assert response.content == response_message # And the guess was not added updated_game = games_repository.get(guild_id=guild_id, game_id=game_id) assert len(updated_game.guesses) == 0
async def test_post_without_channel_uses_event_channel(): # Given guild_id = 1001 game_id = 'game-1' event_channel_id = 50001 # We have a game game = Game(guild_id=guild_id, game_id=game_id) games_repository = FakeGamesRepository([game]) post_embed = discord.Embed() message_provider = MagicMock(MessageProvider) message_provider.game_post_embed.return_value = post_embed discord_messaging = FakeDiscordMessaging() post_route = PostRoute( games_repository=games_repository, discord_messaging=discord_messaging, message_provider=message_provider, ) # When we post a channel message without an explicit target channel event = _make_event(guild_id=guild_id, channel_id=event_channel_id, options={'game-id': game_id}) await post_route.call(event) # Then a message for that game is posted in the channel we sent this command from assert { 'channel_id': event_channel_id, 'embed': post_embed } in discord_messaging.sent_channel_messages
async def test_post_saves_message_id_to_game(): # Given guild_id = 1001 game_id = 'game-1' channel_id = 50001 new_message_id = 1000 discord_messaging = FakeDiscordMessaging() discord_messaging.created_channel_message_id = 1000 game = Game(guild_id=guild_id, game_id=game_id) games_repository = FakeGamesRepository([game]) post_route = PostRoute( games_repository=games_repository, discord_messaging=discord_messaging, message_provider=FakeMessageProvider(), ) # When event = _make_event(guild_id=guild_id, options={ 'game-id': game_id, 'channel': channel_id, }) await post_route.call(event) # Then updated_game = games_repository.get(guild_id, game_id) assert updated_game.channel_messages is not None assert any(channel_message.channel_id == channel_id and channel_message.message_id == new_message_id for channel_message in updated_game.channel_messages)
async def call(self, event: DiscordEvent) -> DiscordResponse: guild_id = event.guild_id game_id = event.command.options.get('game-id') title = event.command.options.get('title') description = event.command.options.get('description') min_guess = event.command.options.get('min') max_guess = event.command.options.get('max') if game_id is None: game_id = self._generate_game_id(guild_id) else: existing_game = self.games_repository.get(guild_id, game_id) if existing_game is not None: message = self.message_provider.duplicate_game_id(game_id) return DiscordResponse.ephemeral_channel_message(message) game = Game( guild_id=guild_id, game_id=game_id, title=title, description=description, min_guess=min_guess, max_guess=max_guess, created_by=event.member.user_id, create_datetime=datetime.now(), close_datetime=None, closed=False, ) self.games_repository.save(game) game_created_message = self.message_provider.game_created(game) return DiscordResponse.ephemeral_channel_message(game_created_message)
def test_get_minimal_game(): # Given games_repository = GamesRepositoryImpl(table_name=TABLE_NAME, host=HOST) game = Game( guild_id=1, game_id="game-1", created_by=10 ) # When games_repository.save(game) retrieved_game = games_repository.get(game.guild_id, game.game_id) # Then assert game.guild_id == retrieved_game.guild_id assert game.game_id == retrieved_game.game_id assert not game.is_numeric()
def test_get_unknown_game_returns_none(): # Given: an empty database games_repository = GamesRepositoryImpl(table_name=TABLE_NAME, host=HOST) other_game = Game(guild_id=1, game_id='game-id-1', created_by=10) # When games_repository.save(other_game) game = games_repository.get(1, 'game-id-2') # Then assert game is None
async def test_edit_guess(): # Given guild_id = 10 game_id = "game-1" guessing_player_id = 100 old_guess = "<My Guess>" guess_edited_message = "Guess has been edited." message_provider = MagicMock(MessageProvider) message_provider.guess_edited.return_value = guess_edited_message game = Game( guild_id=guild_id, game_id=game_id, guesses={ guessing_player_id: GameGuess( user_id=guessing_player_id, guess=old_guess, ) } ) games_repository = FakeGamesRepository(games=[game]) game_post_manager = AsyncMock(GamePostManager) route = _route( games_repository=games_repository, message_provider=message_provider, game_post_manager=game_post_manager, ) # When new_guess = "500" event = _make_event( guild_id=guild_id, options={ 'game-id': game_id, 'member': str(guessing_player_id), 'guess': new_guess, } ) response = await route.call(event) # Then updated_game = games_repository.get(guild_id, game_id) assert updated_game.guesses[guessing_player_id].guess == new_guess game_post_manager.update.assert_called_with(game) assert response.is_ephemeral assert response.content == guess_edited_message
async def test_edit_guess_does_not_exist(): # Given guild_id = 10 game_id = "game-1" delete_member_id = 200 guess_not_found_message = "No guess found." message_provider = MagicMock(MessageProvider) message_provider.error_guess_not_found.return_value = guess_not_found_message game = Game( guild_id=guild_id, game_id=game_id, guesses={ -1: GameGuess( user_id=-1, guess="<your guess>", ) } ) games_repository = FakeGamesRepository(games=[game]) game_post_manager = AsyncMock(GamePostManager) route = _route( games_repository=games_repository, message_provider=message_provider, game_post_manager=game_post_manager, ) # When event = _make_event( guild_id=guild_id, options={ 'game-id': game_id, 'member': str(delete_member_id), 'guess': "some new guess", } ) response = await route.call(event) # Then game_post_manager.update.assert_not_called() assert response.is_ephemeral assert response.content == guess_not_found_message
async def test_guess_duplicate_guess(): # Given guild_id = 1000 game_id = 'game-id' event_channel_id = 7002 guessing_user_id = 2000 member = DiscordMember(user_id=guessing_user_id, ) old_guess = '100' new_guess = '100' existing_guesses = { guessing_user_id: GameGuess(user_id=guessing_user_id, guess=old_guess), } # We have a game existing_game = Game(guild_id=guild_id, game_id=game_id, guesses=existing_guesses) games_repository = FakeGamesRepository(games=[existing_game]) duplicate_guess_message = "You already placed a guess for this game." message_provider = MagicMock(MessageProvider) message_provider.error_duplicate_guess.return_value = duplicate_guess_message guess_route = _route(games_repository=games_repository, message_provider=message_provider) # When we guess '42' as the same user event = _create_guess_event( guild_id=guild_id, user_id=guessing_user_id, game_id=game_id, guess=new_guess, event_channel_id=event_channel_id, member=member, ) response = await guess_route.call(event) # Then no new guess is added saved_game = games_repository.get(guild_id, existing_game.game_id) assert saved_game.guesses[guessing_user_id].guess == old_guess # And an ephemeral error is the response assert response.is_ephemeral assert response.content == duplicate_guess_message
async def test_close_updates_channel_messages(): guild_id = 1007 game_id = 'game-id-1' # We have an open game with channel messages channel_message_1 = ChannelMessage(channel_id=1, message_id=10) channel_message_2 = ChannelMessage(channel_id=2, message_id=11) game = Game(game_id=game_id, guild_id=guild_id, closed=False, channel_messages=[channel_message_1, channel_message_2]) games_repository = FakeGamesRepository([game]) post_embed = discord.Embed() message_provider = MagicMock(MessageProvider) message_provider.game_post_embed.return_value = post_embed discord_messaging = FakeDiscordMessaging() route = CloseGameRoute( games_repository=games_repository, discord_messaging=discord_messaging, message_provider=message_provider, ) # When we close it event = _make_event(guild_id=guild_id, options={ 'game-id': game.game_id, }) await route.call(event) # Then the channel messages are updated assert len(discord_messaging.updated_channel_messages) == 2 assert { 'channel_id': channel_message_1.channel_id, 'message_id': channel_message_1.message_id, 'embed': post_embed, } in discord_messaging.updated_channel_messages assert { 'channel_id': channel_message_2.channel_id, 'message_id': channel_message_2.message_id, 'embed': post_embed, } in discord_messaging.updated_channel_messages
async def test_guess_closed_game(): # Given guild_id = 1515 game_id = 'closed-game' guessing_user_id = 3000 other_user_id = 3050 event_channel_id = 7775 member = DiscordMember() # We have a closed game game = Game(game_id=game_id, guild_id=guild_id, closed=True, guesses={ other_user_id: GameGuess(), }) games_repository = FakeGamesRepository([game]) duplicate_guess_message = "This game has been closed for new guesses." message_provider = MagicMock(MessageProvider) message_provider.error_guess_on_closed_game.return_value = duplicate_guess_message route = _route( games_repository=games_repository, message_provider=message_provider, ) # When event = _create_guess_event( guild_id=guild_id, game_id=game_id, user_id=guessing_user_id, event_channel_id=event_channel_id, member=member, ) response = await route.call(event) # Then: no guess is added saved_game = games_repository.get(guild_id, game_id) assert list(saved_game.guesses.keys()) == [other_user_id] # And an ephemeral error is the response assert response.is_ephemeral assert response.content == duplicate_guess_message
async def test_lower_than_min(): # Given guild_id = 1005 user_id = 13000 game_id = 'game-id' min_guess = -5 response_message = "Guess must be a number higher than -5" message_provider = MagicMock(MessageProvider, autospec=True) message_provider.invalid_guess.return_value = response_message # And we have a game with a max game = Game( guild_id=guild_id, game_id=game_id, min_guess=min_guess, ) games_repository = FakeGamesRepository([game]) guess_route = _route( games_repository=games_repository, message_provider=message_provider, ) # When we guess lower than that guess = "-6" event = _create_guess_event( guild_id=guild_id, game_id=game.game_id, user_id=user_id, user_nickname='nickname', guess=guess, ) response = await guess_route.call(event) # Then assert response.is_ephemeral assert response.content == response_message # And the guess was not added updated_game = games_repository.get(guild_id=guild_id, game_id=game_id) assert len(updated_game.guesses) == 0
def _game_from_model(model: EternalGuessesTable) -> Game: guild_id = int(re.match(PK_REGEX, model.pk).group(1)) game_id = re.match(SK_REGEX, model.sk).group(1) create_datetime = None if model.create_datetime is not None: create_datetime = datetime.fromisoformat(model.create_datetime) close_datetime = None if model.close_datetime is not None: close_datetime = datetime.fromisoformat(model.close_datetime) closed = model.closed or False title = model.title or "" description = model.description or "" min_guess = model.min_guess max_guess = model.max_guess channel_messages = [] if model.channel_messages is not None: channel_messages = list( _channel_message_from_model(message_model) for message_model in model.channel_messages) guesses = {} if model.guesses is not None: for (user_id, guess_model) in json.loads(model.guesses).items(): guesses[int(user_id)] = _guess_from_model(guess_model) return Game( guild_id=guild_id, game_id=game_id, title=title, description=description, min_guess=min_guess, max_guess=max_guess, created_by=model.created_by, create_datetime=create_datetime, close_datetime=close_datetime, closed=closed, channel_messages=channel_messages, guesses=guesses, )
async def test_guess_updates_game_guesses(mock_datetime): # Given game_id = 'game-id' guild_id = 1000 other_user_id = 5000 user_id = 100 user_nickname = 'user-1' guess_answer = '42' guess_timestamp = datetime.now() mock_datetime.now.return_value = guess_timestamp existing_game = Game(guild_id=guild_id, game_id=game_id, guesses={ other_user_id: GameGuess(), }) fake_games_repository = FakeGamesRepository([existing_game]) guess_route = _route(games_repository=fake_games_repository, ) # When we make a guess event = _create_guess_event(guild_id=guild_id, game_id=game_id, user_id=user_id, user_nickname=user_nickname, guess=guess_answer) await guess_route.call(event) # Then saved_game = fake_games_repository.get(guild_id, game_id) assert saved_game.guild_id == guild_id assert saved_game.game_id == game_id assert other_user_id in saved_game.guesses.keys() saved_guess = saved_game.guesses[user_id] assert saved_guess.user_id == user_id assert saved_guess.user_nickname == user_nickname assert saved_guess.timestamp == guess_timestamp assert saved_guess.guess == guess_answer
async def test_post_creates_channel_message(): # Given guild_id = 1001 game_id = 'game-1' channel_id = 50001 # We have a game game = Game(guild_id=guild_id, game_id=game_id) games_repository = FakeGamesRepository([game]) # And we have a mock message provider post_embed = discord.Embed() message_provider = MagicMock(MessageProvider) message_provider.game_post_embed.return_value = post_embed discord_messaging = FakeDiscordMessaging() post_route = PostRoute( games_repository=games_repository, discord_messaging=discord_messaging, message_provider=message_provider, ) # When we post a channel message for our game with an explicit channel event = _make_event(guild_id=guild_id, options={ 'game-id': game_id, 'channel': channel_id, }) await post_route.call(event) # Then a message about that game is posted in the given channel assert { 'channel_id': channel_id, 'embed': post_embed } in discord_messaging.sent_channel_messages # And the channel id is saved in the game saved_game = games_repository.get(guild_id, game_id) assert len(saved_game.channel_messages) == 1
def test_get_all_games(): # Given games_repository = GamesRepositoryImpl(table_name=TABLE_NAME, host=HOST) guild_id = 50000 game_1_id = 'game-1' game_1_created_by = 10 game_1_channel_message_channel_id = 1000 game_1_channel_message_message_id = 2000 game_1_create_datetime = datetime(2020, 2, 7) game_2_id = 'game-2' game_2_created_by = 20 game_2_guess_user_id = 3000 game_2_guess_user_nick = 'user-nick' game_2_guess_answer = 'guess-answer' game_2_guess_timestamp = datetime(2021, 2, 3, 10, 10, 10) game_2_create_datetime = datetime(2021, 2, 2) game_1 = Game( guild_id=guild_id, game_id=game_1_id, created_by=game_1_created_by, create_datetime=game_1_create_datetime, channel_messages=[ ChannelMessage(channel_id=game_1_channel_message_channel_id, message_id=game_1_channel_message_message_id), ] ) game_2 = Game( guild_id=guild_id, game_id=game_2_id, created_by=game_2_created_by, create_datetime=game_2_create_datetime, guesses={ game_2_guess_user_id: GameGuess( user_id=game_2_guess_user_id, nickname=game_2_guess_user_nick, guess=game_2_guess_answer, timestamp=game_2_guess_timestamp ) } ) # When games_repository.save(game_1) games_repository.save(game_2) games = games_repository.get_all(guild_id) # Then assert len(games) == 2 game_1 = None game_2 = None for game in games: if game.game_id == game_1_id: game_1 = game elif game.game_id == game_2_id: game_2 = game assert game_1 is not None assert game_1.guild_id == guild_id assert game_1.game_id == game_1_id assert game_1.created_by == game_1_created_by assert game_1.create_datetime == game_1_create_datetime assert game_1.close_datetime is None assert game_1.closed is False assert game_1.guesses == {} assert len(game_1.channel_messages) == 1 assert game_1.channel_messages[0].channel_id == game_1_channel_message_channel_id assert game_1.channel_messages[0].message_id == game_1_channel_message_message_id assert game_2 is not None assert game_2.guild_id == guild_id assert game_2.game_id == game_2_id assert game_2.created_by == game_2_created_by assert game_2.create_datetime == game_2_create_datetime assert game_2.close_datetime is None assert game_2.closed is False assert game_2.channel_messages == [] assert game_2_guess_user_id in game_2.guesses game_guess = game_2.guesses[game_2_guess_user_id] assert game_guess.user_id == game_2_guess_user_id assert game_guess.nickname == game_2_guess_user_nick assert game_guess.guess == game_2_guess_answer assert game_guess.timestamp == game_2_guess_timestamp
def test_get_game(): # Given games_repository = GamesRepositoryImpl(table_name=TABLE_NAME, host=HOST) guild_id = 50 created_by = 120 game_id = 'game-1' user_id = 12123 user_nickname = 'nickname' guess_answer = 'user-guess' guess_timestamp = datetime.now() message_channel_id = 1000 message_message_id = 2000 create_datetime = datetime(2021, 1, 1, 10, 12, 45) close_datetime = datetime(2021, 1, 5, 8, 6, 3) closed = True title = 'A mockery of fools' description = 'A tale as old as time' min_guess = 1 max_guess = 500 game = Game( guild_id=guild_id, created_by=created_by, create_datetime=create_datetime, close_datetime=close_datetime, closed=closed, game_id=game_id, title=title, description=description, min_guess=min_guess, max_guess=max_guess, guesses={ user_id: GameGuess( user_id=user_id, nickname=user_nickname, guess=guess_answer, timestamp=guess_timestamp ) }, channel_messages=[ ChannelMessage(channel_id=message_channel_id, message_id=message_message_id) ] ) # When games_repository.save(game) retrieved_game = games_repository.get(guild_id, game_id) # Then assert retrieved_game.guild_id == guild_id assert retrieved_game.game_id == game_id assert retrieved_game.created_by == created_by assert retrieved_game.create_datetime == create_datetime assert retrieved_game.close_datetime == close_datetime assert retrieved_game.closed is closed assert retrieved_game.title == title assert retrieved_game.description == description assert retrieved_game.min_guess == min_guess assert retrieved_game.max_guess == max_guess assert retrieved_game.is_numeric() assert user_id in retrieved_game.guesses game_guess = retrieved_game.guesses[user_id] assert game_guess.user_id == user_id assert game_guess.nickname == user_nickname assert game_guess.guess == guess_answer assert game_guess.timestamp == guess_timestamp assert len(retrieved_game.channel_messages) == 1 assert retrieved_game.channel_messages[0].channel_id == message_channel_id assert retrieved_game.channel_messages[0].message_id == message_message_id