def setUp(self):
        setup_plugin()

        setup_cvars({
            "qlx_owner": "1234567890",
            "qlx_discordBotToken": "bottoken",
            "qlx_discordRelayChannelIds": "1234",
            "qlx_discordTriggeredChannelIds": "456, 789",
            "qlx_discordUpdateTopicOnTriggeredChannels": "1",
            "qlx_discordKeepTopicSuffixChannelIds": "1234, 456",
            "qlx_discordKeptTopicSuffixes": "{1234: '', 456: ''}",
            "qlx_discordUpdateTopicInterval": "305",
            "qlx_discordTriggerTriggeredChannelChat": "trigger",
            "qlx_discordCommandPrefix": "%",
            "qlx_discordTriggerStatus": "minqlx",
            "qlx_discordMessagePrefix": "[DISCORD]",
            "qlx_discordEnableHelp": "1",
            "qlx_discordEnableVersion": "1",
            "qlx_displayChannelForDiscordRelayChannels": "0",
            "qlx_discordReplaceMentionsForRelayedMessages": "0",
            "qlx_discordReplaceMentionsForTriggeredMessages": "1",
            "qlx_discordAdminPassword": "******",
            "qlx_discordAuthCommand": "auth",
            "qlx_discordExecPrefix": "exec",
            "qlx_discordLogToSeparateLogfile": "0",
            "qlx_discordTriggeredChatMessagePrefix": "",
            "qlx_discordRelayTeamchatChannelIds": "242",
            "qlx_discord_extensions": ""
        })

        self.logger = mock(spec=logging.Logger, strict=False)
        self.discord = SimpleAsyncDiscord("version information", self.logger)

        self.setup_discord_library()
    def test_triggered_message_no_triggered_channels_configured(self):
        setup_cvar("qlx_discordTriggeredChannelIds", "")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great, @member #mention !")

        verify(self.triggered_channel(), times=0).send(any)
    def test_relay_team_chat_message_discord_not_logged_in(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        when(self.discord_client).is_ready().thenReturn(False)

        relay_channel = self.relay_teamchat_channel()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, @member #mention !")

        verify(relay_channel, times=0).send(any)
    def test_find_user_match_case_insensitive_match():
        case_insensitive_matching_user = mocked_user(name="uSeR")
        other_user = mocked_user(name="non-matched user")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [case_insensitive_matching_user, other_user])

        assert_that(matched_user, is_(case_insensitive_matching_user))
    def test_find_user_match_case_insensitive_nick_match():
        exact_matching_user = mocked_user(name="non-matching name", nick="UseR")
        other_user = mocked_user(name="non-matched user", nick="non-matched nick")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [exact_matching_user, other_user])

        assert_that(matched_user, is_(exact_matching_user))
    def test_find_user_match_exact_match():
        exact_matching_user = mocked_user(name="user")
        other_user = mocked_user(name="non-exact-match-User")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [exact_matching_user, other_user])

        assert_that(matched_user, is_(exact_matching_user))
    def test_relay_team_chat_message_mentioned_channel_not_found(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_teamchat_channel()

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, #mention !")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great, #mention !")
    def test_find_user_match_fuzzy_match_on_nick():
        fuzzy_matching_user = mocked_user(name="non-matchin-usr", nick="matching-General-uSeR")
        other_user = mocked_user(name="non-matched channel")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("UsEr", [fuzzy_matching_user, other_user])

        assert_that(matched_user, is_(fuzzy_matching_user))
    def test_find_channel_match_fuzzy_match():
        fuzzy_matching_channel = mocked_channel(name="matching-GeneRal-channel")
        other_channel = mocked_channel(name="non-matched channel")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [fuzzy_matching_channel, other_channel])

        assert_that(matched_channel, is_(fuzzy_matching_channel))
    def test_find_channel_match_exact_match():
        exact_matching_channel = mocked_channel(name="general")
        other_channel = mocked_channel(name="General")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [exact_matching_channel, other_channel])

        assert_that(matched_channel, is_(exact_matching_channel))
    def test_triggered_message_no_replacement_configured(self):
        setup_cvar("qlx_discordReplaceMentionsForTriggeredMessages", "0")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        trigger_channel1 = self.triggered_channel()

        mentioned_channel = mocked_channel(_id=456, name="mentioned-channel")
        self.setup_discord_members()
        self.setup_discord_channels(mentioned_channel)

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great, @member #mention !")

        assert_text_was_sent_to_discord_channel(
            trigger_channel1,
            "**Chatting player**: QL is great, @member #mention !")
    def test_find_channel_match_case_insensitive_match():
        case_insensitive_matching_channel = mocked_channel(name="GeNeRaL")
        other_channel = mocked_channel(name="non-matched General")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [case_insensitive_matching_channel,
                                                                        other_channel])

        assert_that(matched_channel, is_(case_insensitive_matching_channel))
    def test_find_channel_match_more_than_one_channel_found_and_player_informed():
        sending_player = fake_player(steam_id=1, name="Player")
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="matched_general"),
                                                                        mocked_channel(name="another-matched-general")],
                                                                       sending_player)

        verify(sending_player).tell("Found ^62^7 matching discord channels for #general:")
        verify(sending_player).tell("#matched_general #another-matched-general ")
        assert_that(matched_channel, is_(None))
    def test_relay_chat_message_does_not_replace_all_everyone_and_here(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        unmentioned_user = mocked_user(_id=123, name="chatter")
        self.setup_discord_members(unmentioned_user)
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, @all @everyone @here !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            "**Chatting player**: QL is great, @all @everyone @here !")
    def test_find_user_match_more_than_one_user_found_and_player_informed():
        sending_player = fake_player(steam_id=1, name="Player")
        matched_user = SimpleAsyncDiscord.find_user_that_matches("user",
                                                                 [mocked_user(name="matched_user"),
                                                                  mocked_user(name="another-matched-uSEr")],
                                                                 sending_player)

        verify(sending_player).tell("Found ^62^7 matching discord users for @user:"******"@matched_user @another-matched-uSEr ")
        assert_that(matched_user, is_(None))
    def test_find_user_match_no_match_found():
        matched_user = SimpleAsyncDiscord.find_user_that_matches("awesome",
                                                                 [mocked_user(name="no_match-user"),
                                                                  mocked_user(name="non-matched user")])

        assert_that(matched_user, is_(None))
    def test_find_user_match_more_than_one_user_found():
        matched_user = SimpleAsyncDiscord.find_user_that_matches("user",
                                                                 [mocked_user(name="matched_user"),
                                                                  mocked_user(name="another-matched-uSEr")])

        assert_that(matched_user, is_(None))
class SimpleAsyncDiscordTests(unittest.IsolatedAsyncioTestCase):

    def setUp(self):
        setup_plugin()

        setup_cvars({
            "qlx_owner": "1234567890",
            "qlx_discordBotToken": "bottoken",
            "qlx_discordRelayChannelIds": "1234",
            "qlx_discordTriggeredChannelIds": "456, 789",
            "qlx_discordUpdateTopicOnTriggeredChannels": "1",
            "qlx_discordKeepTopicSuffixChannelIds": "1234, 456",
            "qlx_discordKeptTopicSuffixes": "{1234: '', 456: ''}",
            "qlx_discordUpdateTopicInterval": "305",
            "qlx_discordTriggerTriggeredChannelChat": "trigger",
            "qlx_discordCommandPrefix": "%",
            "qlx_discordTriggerStatus": "minqlx",
            "qlx_discordMessagePrefix": "[DISCORD]",
            "qlx_discordEnableHelp": "1",
            "qlx_discordEnableVersion": "1",
            "qlx_displayChannelForDiscordRelayChannels": "0",
            "qlx_discordReplaceMentionsForRelayedMessages": "0",
            "qlx_discordReplaceMentionsForTriggeredMessages": "1",
            "qlx_discordAdminPassword": "******",
            "qlx_discordAuthCommand": "auth",
            "qlx_discordExecPrefix": "exec",
            "qlx_discordLogToSeparateLogfile": "0",
            "qlx_discordTriggeredChatMessagePrefix": "",
            "qlx_discordRelayTeamchatChannelIds": "242",
            "qlx_discord_extensions": ""
        })

        self.logger = mock(spec=logging.Logger, strict=False)
        self.discord = SimpleAsyncDiscord("version information", self.logger)

        self.setup_discord_library()

    def tearDown(self):
        self.discord_client.loop.close()
        unstub()

    def setup_discord_client_mock_common(self):
        self.discord_client = mock(spec=Bot, strict=False)

        self.discord_client.change_presence = AsyncMock()
        self.discord_client.tree = mock(spec=discord.app_commands.CommandTree, strict=False)
        self.discord_client.tree.sync = AsyncMock()

        self.discord_client.user = mock(User, strict=False)
        self.discord_client.user.name = "Bot Name"
        self.discord_client.user.id = 24680

        self.discord_client.loop = asyncio.new_event_loop()

    def setup_discord_library(self):
        self.setup_discord_client_mock_common()

        when(self.discord_client).is_ready().thenReturn(True)
        when(self.discord_client).is_closed().thenReturn(False)

        self.discord.discord = self.discord_client

    def mocked_context(self, prefix="%", bot=None, message=mocked_message(), invoked_with="asdf"):
        context = mock({'send': AsyncMock()})
        context.prefix = prefix
        context.bot = self.discord_client
        if bot is not None:
            context.bot = bot
        context.message = message
        context.invoked_with = invoked_with

        return context

    def relay_channel(self):
        channel = mocked_channel(_id=1234, name="relay-channel")

        when(self.discord_client).get_channel(channel.id).thenReturn(channel)

        return channel

    def relay_teamchat_channel(self):
        channel = mocked_channel(_id=242, name="relay-teamchat-channel")

        when(self.discord_client).get_channel(channel.id).thenReturn(channel)

        return channel

    def triggered_channel(self):
        channel = mocked_channel(_id=456, name="triggered-channel")

        when(self.discord_client).get_channel(channel.id).thenReturn(channel)

        return channel

    def uninteresting_channel(self):
        channel = mocked_channel(_id=987, name="uninteresting-channel")

        when(self.discord_client).get_channel(channel.id).thenReturn(channel)

        return channel

    def setup_discord_members(self, *users):
        when(self.discord_client).get_all_members().thenReturn(list(users))

    def setup_discord_channels(self, *channels):
        when(self.discord_client).get_all_channels().thenReturn(list(channels))

    def verify_added_command(self, name, callback, checks=None):
        if checks is None:
            verify(self.discord_client).add_command(
                arg_that(lambda command: command.name == name and command.callback == callback))
        else:
            verify(self.discord_client).add_command(
                arg_that(lambda command:
                         command.name == name and command.callback == callback and command.checks == checks))

    def test_status_connected(self):
        status = self.discord.status()

        assert_that(status, is_("Discord connection up and running."))

    def test_status_no_client(self):
        self.discord.discord = None

        status = self.discord.status()

        assert_that(status, is_("No discord connection set up."))

    def test_status_client_not_connected(self):
        when(self.discord_client).is_ready().thenReturn(False)

        status = self.discord.status()

        assert_that(status, is_("Discord client not connected."))

    async def test_initialize_bot(self):
        self.discord.initialize_bot(self.discord_client)

        self.verify_added_command(name="version", callback=self.discord.version)
        verify(self.discord_client).add_listener(self.discord.on_ready)
        verify(self.discord_client).add_listener(self.discord.on_message)

    async def test_disable_version(self):
        self.discord.discord_version_enabled = False
        self.discord.initialize_bot(self.discord_client)

        verify(self.discord_client, times=0).add_command(name="version", callback=self.discord.version)

    async def test_version(self):
        context = self.mocked_context()

        await self.discord.version(context)

        assert_matching_string_send_to_discord_context(context, "```version information```")

    async def test_on_ready(self):
        self.setup_discord_library()

        await self.discord.on_ready()

        self.discord_client.change_presence.assert_called_once_with(activity=discord.Game(name="Quake Live"))

    async def test_on_message_is_relayed(self):
        message = mocked_message(content="some chat message",
                                 user=mocked_user(name="Sender"),
                                 channel=self.relay_channel())

        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(message)

        verify(minqlx.CHAT_CHANNEL).reply("[DISCORD] ^6Sender^7:^2 some chat message")

    async def test_on_message_by_user_with_nickname(self):
        message = mocked_message(content="some chat message",
                                 user=mocked_user(name="Sender", nick="SenderNick"),
                                 channel=self.relay_channel())

        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(message)

        verify(minqlx.CHAT_CHANNEL).reply("[DISCORD] ^6SenderNick^7:^2 some chat message")

    async def test_on_message_in_wrong_channel(self):
        message = mocked_message(content="some chat message",
                                 channel=self.triggered_channel())

        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(message)

        verify(minqlx.CHAT_CHANNEL, times=0).reply(any)

    async def test_on_message_too_short_message(self):
        message = mocked_message(content="",
                                 channel=self.relay_channel())

        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(message)

        verify(minqlx.CHAT_CHANNEL, times=0).reply(any)

    async def test_on_message_from_bot(self):
        message = mocked_message(content="",
                                 user=self.discord_client.user,
                                 channel=self.relay_channel())

        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(message)

        verify(minqlx.CHAT_CHANNEL, times=0).reply(any)

    async def test_on_message_without_message(self):
        patch(minqlx.CHAT_CHANNEL, "reply", lambda msg: None)
        when2(minqlx.CHAT_CHANNEL.reply, any).thenReturn(None)

        await self.discord.on_message(None)

        verify(minqlx.CHAT_CHANNEL, times=0).reply(any)

    def test_stop_discord_client(self):
        self.discord_client.close = AsyncMock()

        self.discord.stop()

        self.discord_client.change_presence.assert_called_once_with(status=Status.offline)
        self.discord_client.close.assert_called_once()

    def test_stop_discord_client_discord_not_initialized(self):
        self.discord.discord = None

        self.discord.stop()

        verify(self.discord_client, times=0).change_presence(status="offline")
        verify(self.discord_client, times=0).logout()

    def test_relay_message(self):
        relay_channel = self.relay_channel()

        self.discord.relay_message("awesome relayed message")

        assert_text_was_sent_to_discord_channel(relay_channel, "awesome relayed message")

    def test_relay_message_with_not_connected_client(self):
        when(self.discord_client).is_ready().thenReturn(False)

        relay_channel = self.relay_channel()

        self.discord.relay_message("awesome relayed message")

        verify(relay_channel, times=0).send(any)

    def test_send_to_discord_channels_with_no_channel_ids(self):
        self.discord.send_to_discord_channels(set(), "awesome relayed message")

        verify(self.discord_client, times=0)

    def test_send_to_discord_channels_for_non_existing_channel(self):
        self.discord.send_to_discord_channels({"6789"}, "awesome relayed message")

        verify(self.discord_client, times=0).send_message(any, any)

    def test_relay_chat_message_simple_message(self):
        relay_channel = self.relay_channel()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great!")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great!")

    def test_relay_chat_message_with_asterisks_in_playername(self):
        relay_channel = self.relay_channel()

        player = fake_player(steam_id=1, name="*Chatting* player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great!")

        assert_text_was_sent_to_discord_channel(relay_channel, r"**\*Chatting\* player**: QL is great!")

    def test_relay_chat_message_replace_user_mention(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        mentioned_user = mocked_user(_id=123, name="chatter")
        self.setup_discord_members(mentioned_user)
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, @chatter !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            f"**Chatting player**: QL is great, {mentioned_user.mention} !")

    def test_relay_chat_message_mentioned_member_not_found(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, @chatter !")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great, @chatter !")

    def test_relay_chat_message_does_not_replace_all_everyone_and_here(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        unmentioned_user = mocked_user(_id=123, name="chatter")
        self.setup_discord_members(unmentioned_user)
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, @all @everyone @here !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            "**Chatting player**: QL is great, @all @everyone @here !")

    def test_relay_chat_message_replace_channel_mention(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        mentioned_channel = mocked_channel(_id=456, name="mentioned-channel")
        self.setup_discord_members()
        self.setup_discord_channels(mentioned_channel)

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, #mention !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            f"**Chatting player**: QL is great, {mentioned_channel.mention} !")

    def test_relay_chat_message_mentioned_channel_not_found(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_channel()

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, #mention !")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great, #mention !")

    def test_relay_chat_message_discord_not_logged_in(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        when(self.discord_client).is_ready().thenReturn(False)

        relay_channel = self.relay_channel()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_chat_message(player, minqlx_channel, "QL is great, @member #mention !")

        verify(relay_channel, times=0).send(any)

    def test_relay_team_chat_message_simple_message(self):
        relay_channel = self.relay_teamchat_channel()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great!")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great!")

    def test_relay_team_chat_message_with_asterisks_in_playername(self):
        relay_channel = self.relay_teamchat_channel()

        player = fake_player(steam_id=1, name="*Chatting* player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great!")

        assert_text_was_sent_to_discord_channel(relay_channel, r"**\*Chatting\* player**: QL is great!")

    def test_relay_team_chat_message_replace_user_mention(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_teamchat_channel()

        mentioned_user = mocked_user(_id=123, name="chatter")
        self.setup_discord_members(mentioned_user)
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, @chatter !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            f"**Chatting player**: QL is great, {mentioned_user.mention} !")

    def test_relay_team_chat_message_mentioned_member_not_found(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_teamchat_channel()

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, @chatter !")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great, @chatter !")

    def test_relay_team_chat_message_replace_channel_mention(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_teamchat_channel()

        mentioned_channel = mocked_channel(_id=456, name="mentioned-channel")
        self.setup_discord_members()
        self.setup_discord_channels(mentioned_channel)

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, #mention !")

        assert_text_was_sent_to_discord_channel(
            relay_channel,
            f"**Chatting player**: QL is great, {mentioned_channel.mention} !")

    def test_relay_team_chat_message_mentioned_channel_not_found(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        relay_channel = self.relay_teamchat_channel()

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, #mention !")

        assert_text_was_sent_to_discord_channel(relay_channel, "**Chatting player**: QL is great, #mention !")

    def test_relay_team_chat_message_discord_not_logged_in(self):
        setup_cvar("qlx_discordReplaceMentionsForRelayedMessages", "1")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        when(self.discord_client).is_ready().thenReturn(False)

        relay_channel = self.relay_teamchat_channel()

        player = fake_player(steam_id=1, name="Chatting player")
        minqlx_channel = ""

        self.discord.relay_team_chat_message(player, minqlx_channel, "QL is great, @member #mention !")

        verify(relay_channel, times=0).send(any)

    @staticmethod
    def test_find_user_match_exact_match():
        exact_matching_user = mocked_user(name="user")
        other_user = mocked_user(name="non-exact-match-User")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [exact_matching_user, other_user])

        assert_that(matched_user, is_(exact_matching_user))

    @staticmethod
    def test_find_user_match_case_insensitive_match():
        case_insensitive_matching_user = mocked_user(name="uSeR")
        other_user = mocked_user(name="non-matched user")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [case_insensitive_matching_user, other_user])

        assert_that(matched_user, is_(case_insensitive_matching_user))

    @staticmethod
    def test_find_user_match_exact_nick_match():
        exact_matching_user = mocked_user(name="non-matching name", nick="user")
        other_user = mocked_user(name="non-exact-match-User")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [exact_matching_user, other_user])

        assert_that(matched_user, is_(exact_matching_user))

    @staticmethod
    def test_find_user_match_case_insensitive_nick_match():
        exact_matching_user = mocked_user(name="non-matching name", nick="UseR")
        other_user = mocked_user(name="non-matched user", nick="non-matched nick")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("user", [exact_matching_user, other_user])

        assert_that(matched_user, is_(exact_matching_user))

    @staticmethod
    def test_find_user_match_fuzzy_match_on_name():
        fuzzy_matching_user = mocked_user(name="matching-GeneRal-user")
        other_user = mocked_user(name="non-matched channel")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("UsEr", [fuzzy_matching_user, other_user])

        assert_that(matched_user, is_(fuzzy_matching_user))

    @staticmethod
    def test_find_user_match_fuzzy_match_on_nick():
        fuzzy_matching_user = mocked_user(name="non-matchin-usr", nick="matching-General-uSeR")
        other_user = mocked_user(name="non-matched channel")

        matched_user = SimpleAsyncDiscord.find_user_that_matches("UsEr", [fuzzy_matching_user, other_user])

        assert_that(matched_user, is_(fuzzy_matching_user))

    @staticmethod
    def test_find_user_match_no_match_found():
        matched_user = SimpleAsyncDiscord.find_user_that_matches("awesome",
                                                                 [mocked_user(name="no_match-user"),
                                                                  mocked_user(name="non-matched user")])

        assert_that(matched_user, is_(None))

    @staticmethod
    def test_find_user_match_more_than_one_user_found():
        matched_user = SimpleAsyncDiscord.find_user_that_matches("user",
                                                                 [mocked_user(name="matched_user"),
                                                                  mocked_user(name="another-matched-uSEr")])

        assert_that(matched_user, is_(None))

    @staticmethod
    def test_find_user_match_more_than_one_user_found_and_player_informed():
        sending_player = fake_player(steam_id=1, name="Player")
        matched_user = SimpleAsyncDiscord.find_user_that_matches("user",
                                                                 [mocked_user(name="matched_user"),
                                                                  mocked_user(name="another-matched-uSEr")],
                                                                 sending_player)

        verify(sending_player).tell("Found ^62^7 matching discord users for @user:"******"@matched_user @another-matched-uSEr ")
        assert_that(matched_user, is_(None))

    @staticmethod
    def test_find_channel_match_exact_match():
        exact_matching_channel = mocked_channel(name="general")
        other_channel = mocked_channel(name="General")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [exact_matching_channel, other_channel])

        assert_that(matched_channel, is_(exact_matching_channel))

    @staticmethod
    def test_find_channel_match_case_insensitive_match():
        case_insensitive_matching_channel = mocked_channel(name="GeNeRaL")
        other_channel = mocked_channel(name="non-matched General")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [case_insensitive_matching_channel,
                                                                        other_channel])

        assert_that(matched_channel, is_(case_insensitive_matching_channel))

    @staticmethod
    def test_find_channel_match_fuzzy_match():
        fuzzy_matching_channel = mocked_channel(name="matching-GeneRal-channel")
        other_channel = mocked_channel(name="non-matched channel")

        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [fuzzy_matching_channel, other_channel])

        assert_that(matched_channel, is_(fuzzy_matching_channel))

    @staticmethod
    def test_find_channel_match_no_match_found():
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="no_match-channel"),
                                                                        mocked_channel(name="non-matched channel")])

        assert_that(matched_channel, is_(None))

    @staticmethod
    def test_find_channel_match_more_than_one_channel_found():
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="matched_general"),
                                                                        mocked_channel(name="another-matched-general")])

        assert_that(matched_channel, is_(None))

    @staticmethod
    def test_find_channel_match_more_than_one_channel_found_and_player_informed():
        sending_player = fake_player(steam_id=1, name="Player")
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="matched_general"),
                                                                        mocked_channel(name="another-matched-general")],
                                                                       sending_player)

        verify(sending_player).tell("Found ^62^7 matching discord channels for #general:")
        verify(sending_player).tell("#matched_general #another-matched-general ")
        assert_that(matched_channel, is_(None))

    def test_triggered_message(self):
        trigger_channel1 = self.triggered_channel()

        trigger_channel2 = mocked_channel(_id=789)
        when(self.discord_client).get_channel(trigger_channel2.id).thenReturn(trigger_channel2)

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great!")

        assert_text_was_sent_to_discord_channel(trigger_channel1, "**Chatting player**: QL is great!")
        assert_text_was_sent_to_discord_channel(trigger_channel2, "**Chatting player**: QL is great!")

    def test_triggered_message_with_escaped_playername(self):
        trigger_channel1 = self.triggered_channel()

        trigger_channel2 = mocked_channel(_id=789)
        when(self.discord_client).get_channel(trigger_channel2.id).thenReturn(trigger_channel2)

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="*Chatting_player*")

        self.discord.triggered_message(player, "QL is great!")

        assert_text_was_sent_to_discord_channel(trigger_channel1, r"**\*Chatting\_player\***: QL is great!")
        assert_text_was_sent_to_discord_channel(trigger_channel2, r"**\*Chatting\_player\***: QL is great!")

    def test_triggered_message_replaces_mentions(self):
        trigger_channel1 = self.triggered_channel()

        mentioned_user = mocked_user(_id=123, name="chatter")
        mentioned_channel = mocked_channel(_id=456, name="mentioned-channel")
        self.setup_discord_members(mentioned_user)
        self.setup_discord_channels(mentioned_channel)

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great, @chatter #mention !")

        assert_text_was_sent_to_discord_channel(
            trigger_channel1,
            f"**Chatting player**: QL is great, {mentioned_user.mention} {mentioned_channel.mention} !")

    def test_triggered_message_no_triggered_channels_configured(self):
        setup_cvar("qlx_discordTriggeredChannelIds", "")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great, @member #mention !")

        verify(self.triggered_channel(), times=0).send(any)

    def test_triggered_message_no_replacement_configured(self):
        setup_cvar("qlx_discordReplaceMentionsForTriggeredMessages", "0")
        self.discord = SimpleAsyncDiscord("version information", self.logger)
        self.setup_discord_library()

        trigger_channel1 = self.triggered_channel()

        mentioned_channel = mocked_channel(_id=456, name="mentioned-channel")
        self.setup_discord_members()
        self.setup_discord_channels(mentioned_channel)

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great, @member #mention !")

        assert_text_was_sent_to_discord_channel(
            trigger_channel1,
            "**Chatting player**: QL is great, @member #mention !")

    def test_prefixed_triggered_message(self):
        self.discord.discord_triggered_channel_message_prefix = "Server Prefix"
        trigger_channel1 = self.triggered_channel()

        trigger_channel2 = mocked_channel(_id=789)
        when(self.discord_client).get_channel(trigger_channel2.id).thenReturn(trigger_channel2)

        self.setup_discord_members()
        self.setup_discord_channels()

        player = fake_player(steam_id=1, name="Chatting player")

        self.discord.triggered_message(player, "QL is great!")

        assert_text_was_sent_to_discord_channel(trigger_channel1, "Server Prefix **Chatting player**: QL is great!")
        assert_text_was_sent_to_discord_channel(trigger_channel2, "Server Prefix **Chatting player**: QL is great!")
    def test_find_channel_match_more_than_one_channel_found():
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="matched_general"),
                                                                        mocked_channel(name="another-matched-general")])

        assert_that(matched_channel, is_(None))
    def test_find_channel_match_no_match_found():
        matched_channel = SimpleAsyncDiscord.find_channel_that_matches("general",
                                                                       [mocked_channel(name="no_match-channel"),
                                                                        mocked_channel(name="non-matched channel")])

        assert_that(matched_channel, is_(None))