CoolFace
Apppublic

Jack1808/Claude_Code

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
test_discord_platform.py381 linesDownload Raw Back to messaging
1"""Tests for Discord platform adapter."""2 3import asyncio4from unittest.mock import AsyncMock, MagicMock, patch5 6import pytest7 8from messaging.platforms.discord import (9    DISCORD_AVAILABLE,10    DiscordPlatform,11    _get_discord,12    _parse_allowed_channels,13)14 15 16class TestGetDiscord:17    """Tests for _get_discord helper."""18 19    def test_raises_when_discord_not_available(self):20        import messaging.platforms.discord as discord_mod21 22        with (23            patch.object(discord_mod, "DISCORD_AVAILABLE", False),24            patch.object(discord_mod, "_discord_module", None),25            pytest.raises(ImportError, match=r"discord\.py is required"),26        ):27            _get_discord()28 29 30class TestParseAllowedChannels:31    """Tests for _parse_allowed_channels helper."""32 33    def test_empty_string_returns_empty_set(self):34        assert _parse_allowed_channels("") == set()35        assert _parse_allowed_channels(None) == set()36 37    def test_whitespace_only_returns_empty_set(self):38        assert _parse_allowed_channels("   ") == set()39 40    def test_single_channel(self):41        assert _parse_allowed_channels("123456789") == {"123456789"}42 43    def test_comma_separated(self):44        assert _parse_allowed_channels("111,222,333") == {"111", "222", "333"}45 46    def test_strips_whitespace(self):47        assert _parse_allowed_channels(" 111 , 222 ") == {"111", "222"}48 49    def test_empty_parts_ignored(self):50        assert _parse_allowed_channels("111,,222,") == {"111", "222"}51 52 53@pytest.mark.skipif(not DISCORD_AVAILABLE, reason="discord.py not installed")54class TestDiscordPlatform:55    """Tests for DiscordPlatform (requires discord.py)."""56 57    def test_init_with_token(self):58        platform = DiscordPlatform(59            bot_token="test_token",60            allowed_channel_ids="123,456",61        )62        assert platform.bot_token == "test_token"63        assert platform.allowed_channel_ids == {"123", "456"}64 65    def test_init_without_allowed_channels(self):66        with patch.dict("os.environ", {"ALLOWED_DISCORD_CHANNELS": ""}, clear=False):67            platform = DiscordPlatform(bot_token="token", allowed_channel_ids="")68        assert platform.allowed_channel_ids == set()69 70    def test_empty_allowed_channels_rejects_all_messages(self):71        """When allowed_channel_ids is empty, no channels are allowed (secure default)."""72        with patch.dict("os.environ", {"ALLOWED_DISCORD_CHANNELS": ""}, clear=False):73            platform = DiscordPlatform(bot_token="token", allowed_channel_ids="")74        assert platform.allowed_channel_ids == set()75        # Empty set means: not self.allowed_channel_ids is True -> reject76 77    def test_truncate_long_message(self):78        platform = DiscordPlatform(bot_token="token")79        long_text = "x" * 250080        truncated = platform._truncate(long_text)81        assert len(truncated) == 200082        assert truncated.endswith("...")83 84    def test_truncate_short_message_unchanged(self):85        platform = DiscordPlatform(bot_token="token")86        short = "hello"87        assert platform._truncate(short) == short88 89    def test_truncate_exactly_at_limit_unchanged(self):90        platform = DiscordPlatform(bot_token="token")91        exact = "x" * 200092        assert platform._truncate(exact) == exact93 94    def test_truncate_one_over_limit_truncates(self):95        platform = DiscordPlatform(bot_token="token")96        over = "x" * 200197        result = platform._truncate(over)98        assert len(result) == 200099        assert result.endswith("...")100 101    def test_truncate_empty_string(self):102        platform = DiscordPlatform(bot_token="token")103        assert platform._truncate("") == ""104 105    @pytest.mark.asyncio106    async def test_send_message_returns_message_id(self):107        platform = DiscordPlatform(bot_token="token")108        mock_msg = MagicMock()109        mock_msg.id = 999110        mock_channel = AsyncMock()111        mock_channel.send = AsyncMock(return_value=mock_msg)112        platform._connected = True113        with patch.object(114            platform._client, "get_channel", MagicMock(return_value=mock_channel)115        ):116            msg_id = await platform.send_message("123", "Hello")117        assert msg_id == "999"118 119    @pytest.mark.asyncio120    async def test_edit_message(self):121        platform = DiscordPlatform(bot_token="token")122        mock_msg = AsyncMock()123        mock_channel = AsyncMock()124        mock_channel.fetch_message = AsyncMock(return_value=mock_msg)125        platform._connected = True126        with patch.object(127            platform._client, "get_channel", MagicMock(return_value=mock_channel)128        ):129            await platform.edit_message("123", "456", "Updated text")130        mock_msg.edit.assert_called_once_with(content="Updated text")131 132    @pytest.mark.asyncio133    async def test_send_message_channel_not_found_raises(self):134        platform = DiscordPlatform(bot_token="token")135        platform._connected = True136        with (137            patch.object(platform._client, "get_channel", MagicMock(return_value=None)),138            pytest.raises(RuntimeError, match="Channel"),139        ):140            await platform.send_message("123", "Hello")141 142    @pytest.mark.asyncio143    async def test_send_message_channel_no_send_raises(self):144        platform = DiscordPlatform(bot_token="token")145        platform._connected = True146        mock_channel = MagicMock(spec=[])  # No send attr147        with (148            patch.object(149                platform._client, "get_channel", MagicMock(return_value=mock_channel)150            ),151            pytest.raises(RuntimeError, match="Channel"),152        ):153            await platform.send_message("123", "Hello")154 155    @pytest.mark.asyncio156    async def test_queue_send_message_without_limiter_calls_send_message(self):157        platform = DiscordPlatform(bot_token="token")158        platform._limiter = None159        platform._connected = True160        mock_channel = AsyncMock()161        mock_msg = MagicMock()162        mock_msg.id = 42163        mock_channel.send = AsyncMock(return_value=mock_msg)164        with patch.object(165            platform._client, "get_channel", MagicMock(return_value=mock_channel)166        ):167            result = await platform.queue_send_message("123", "hi")168        assert result == "42"169        mock_channel.send.assert_awaited_once()170 171    @pytest.mark.asyncio172    async def test_queue_edit_message_without_limiter_calls_edit_message(self):173        platform = DiscordPlatform(bot_token="token")174        platform._limiter = None175        platform._connected = True176        mock_msg = AsyncMock()177        mock_channel = AsyncMock()178        mock_channel.fetch_message = AsyncMock(return_value=mock_msg)179        with patch.object(180            platform._client, "get_channel", MagicMock(return_value=mock_channel)181        ):182            await platform.queue_edit_message("123", "456", "Updated")183        mock_msg.edit.assert_called_once_with(content="Updated")184 185    @pytest.mark.asyncio186    async def test_on_discord_message_bot_ignored(self):187        platform = DiscordPlatform(bot_token="token", allowed_channel_ids="123")188        handler = AsyncMock()189        platform.on_message(handler)190        msg = MagicMock()191        msg.author.bot = True192        msg.content = "hello"193        msg.channel.id = 123194        await platform._on_discord_message(msg)195        handler.assert_not_called()196 197    @pytest.mark.asyncio198    async def test_on_discord_message_empty_content_ignored(self):199        platform = DiscordPlatform(bot_token="token", allowed_channel_ids="123")200        handler = AsyncMock()201        platform.on_message(handler)202        msg = MagicMock()203        msg.author.bot = False204        msg.content = ""205        msg.channel.id = 123206        await platform._on_discord_message(msg)207        handler.assert_not_called()208 209    @pytest.mark.asyncio210    async def test_on_discord_message_channel_not_allowed_ignored(self):211        platform = DiscordPlatform(bot_token="token", allowed_channel_ids="123")212        handler = AsyncMock()213        platform.on_message(handler)214        msg = MagicMock()215        msg.author.bot = False216        msg.content = "hello"217        msg.channel.id = 999218        await platform._on_discord_message(msg)219        handler.assert_not_called()220 221    @pytest.mark.asyncio222    async def test_on_discord_message_valid_calls_handler(self):223        platform = DiscordPlatform(bot_token="token", allowed_channel_ids="123")224        handler = AsyncMock()225        platform.on_message(handler)226        msg = MagicMock()227        msg.author.bot = False228        msg.author.id = 456229        msg.author.display_name = "User"230        msg.content = "hello"231        msg.channel.id = 123232        msg.id = 789233        msg.reference = None234        await platform._on_discord_message(msg)235        handler.assert_awaited_once()236        call = handler.call_args[0][0]237        assert call.text == "hello"238        assert call.chat_id == "123"239        assert call.user_id == "456"240        assert call.message_id == "789"241        assert call.platform == "discord"242 243    @pytest.mark.asyncio244    async def test_send_message_with_reply_to(self):245        platform = DiscordPlatform(bot_token="token")246        mock_msg = MagicMock()247        mock_msg.id = 999248        mock_channel = AsyncMock()249        mock_channel.send = AsyncMock(return_value=mock_msg)250        platform._connected = True251        with (252            patch.object(253                platform._client, "get_channel", MagicMock(return_value=mock_channel)254            ),255            patch("messaging.platforms.discord._get_discord") as mock_get,256        ):257            mock_discord = MagicMock()258            mock_get.return_value = mock_discord259            msg_id = await platform.send_message("123", "Hello", reply_to="456")260        assert msg_id == "999"261        mock_channel.send.assert_awaited_once()262        call_kw = mock_channel.send.call_args[1]263        assert call_kw.get("reference") is not None264 265    @pytest.mark.asyncio266    async def test_edit_message_not_found_returns_gracefully(self):267        import discord as discord_pkg268 269        platform = DiscordPlatform(bot_token="token")270        mock_channel = AsyncMock()271        mock_resp = MagicMock()272        mock_resp.status = 404273        mock_channel.fetch_message = AsyncMock(274            side_effect=discord_pkg.NotFound(mock_resp, "Not found")275        )276        platform._connected = True277        with patch.object(278            platform._client, "get_channel", MagicMock(return_value=mock_channel)279        ):280            await platform.edit_message("123", "456", "Updated")281        # Should not raise - NotFound is caught and we return282 283    @pytest.mark.asyncio284    async def test_delete_message(self):285        platform = DiscordPlatform(bot_token="token")286        mock_msg = AsyncMock()287        mock_channel = AsyncMock()288        mock_channel.fetch_message = AsyncMock(return_value=mock_msg)289        platform._connected = True290        with (291            patch.object(292                platform._client, "get_channel", MagicMock(return_value=mock_channel)293            ),294            patch("messaging.platforms.discord._get_discord") as mock_get,295        ):296            mock_get.return_value = MagicMock()297            await platform.delete_message("123", "456")298        mock_msg.delete.assert_awaited_once()299 300    @pytest.mark.asyncio301    async def test_fire_and_forget_with_coroutine(self):302        platform = DiscordPlatform(bot_token="token")303 304        async def _task():305            pass306 307        coro = _task()308        with patch("asyncio.create_task") as mock_create:309 310            def _run(c):311                return asyncio.ensure_future(c)312 313            mock_create.side_effect = _run314            platform.fire_and_forget(coro)315            mock_create.assert_called_once()316        await asyncio.sleep(0)317 318    def test_on_message_registers_handler(self):319        platform = DiscordPlatform(bot_token="token")320        handler = AsyncMock()321        platform.on_message(handler)322        assert platform._message_handler is handler323 324    @pytest.mark.asyncio325    async def test_start_requires_token(self):326        with patch.dict("os.environ", {"DISCORD_BOT_TOKEN": ""}, clear=False):327            platform = DiscordPlatform(bot_token="")328            with pytest.raises(ValueError, match="DISCORD_BOT_TOKEN"):329                await platform.start()330 331    @pytest.mark.asyncio332    async def test_start_connects(self):333        platform = DiscordPlatform(bot_token="token")334 335        async def _fake_start(_token):336            platform._connected = True337 338        with (339            patch.object(340                platform._client,341                "start",342                new_callable=AsyncMock,343                side_effect=_fake_start,344            ),345            patch(346                "messaging.limiter.MessagingRateLimiter.get_instance",347                new_callable=AsyncMock,348            ),349        ):350            await platform.start()351        assert platform.is_connected is True352 353    @pytest.mark.asyncio354    async def test_stop_when_already_closed(self):355        platform = DiscordPlatform(bot_token="token")356        platform._connected = True357        with patch.object(358            platform._client, "is_closed", new_callable=MagicMock, return_value=True359        ):360            await platform.stop()361        assert platform.is_connected is False362 363    @pytest.mark.asyncio364    async def test_stop_closes_client(self):365        platform = DiscordPlatform(bot_token="token")366        platform._connected = True367        mock_close = AsyncMock()368        with (369            patch.object(370                platform._client,371                "is_closed",372                new_callable=MagicMock,373                return_value=False,374            ),375            patch.object(platform._client, "close", mock_close),376        ):377            platform._start_task = None378            await platform.stop()379        mock_close.assert_awaited_once()380        assert platform.is_connected is False381