|
17 | 17 | has_contact, |
18 | 18 | has_external_reply, |
19 | 19 | has_link, |
| 20 | + has_media, |
20 | 21 | has_non_whitelisted_inline_keyboard_urls, |
21 | 22 | has_non_whitelisted_link, |
22 | 23 | has_story, |
@@ -135,6 +136,101 @@ def test_no_story_returns_false(self): |
135 | 136 | assert has_story(msg) is False |
136 | 137 |
|
137 | 138 |
|
| 139 | +class TestHasMedia: |
| 140 | + """Tests for the has_media helper function.""" |
| 141 | + |
| 142 | + def test_photo_detected(self): |
| 143 | + """Test that message with photo is detected.""" |
| 144 | + msg = MagicMock(spec=Message) |
| 145 | + msg.photo = [MagicMock()] |
| 146 | + msg.video = None |
| 147 | + msg.animation = None |
| 148 | + msg.document = None |
| 149 | + msg.audio = None |
| 150 | + msg.voice = None |
| 151 | + msg.video_note = None |
| 152 | + |
| 153 | + assert has_media(msg) is True |
| 154 | + |
| 155 | + def test_video_detected(self): |
| 156 | + """Test that message with video is detected.""" |
| 157 | + msg = MagicMock(spec=Message) |
| 158 | + msg.photo = None |
| 159 | + msg.video = MagicMock() |
| 160 | + msg.animation = None |
| 161 | + msg.document = None |
| 162 | + msg.audio = None |
| 163 | + msg.voice = None |
| 164 | + msg.video_note = None |
| 165 | + |
| 166 | + assert has_media(msg) is True |
| 167 | + |
| 168 | + def test_animation_detected(self): |
| 169 | + """Test that message with animation is detected.""" |
| 170 | + msg = MagicMock(spec=Message) |
| 171 | + msg.photo = None |
| 172 | + msg.video = None |
| 173 | + msg.animation = MagicMock() |
| 174 | + msg.document = None |
| 175 | + msg.audio = None |
| 176 | + msg.voice = None |
| 177 | + msg.video_note = None |
| 178 | + |
| 179 | + assert has_media(msg) is True |
| 180 | + |
| 181 | + def test_audio_detected(self): |
| 182 | + """Test that message with audio is detected.""" |
| 183 | + msg = MagicMock(spec=Message) |
| 184 | + msg.photo = None |
| 185 | + msg.video = None |
| 186 | + msg.animation = None |
| 187 | + msg.document = None |
| 188 | + msg.audio = MagicMock() |
| 189 | + msg.voice = None |
| 190 | + msg.video_note = None |
| 191 | + |
| 192 | + assert has_media(msg) is True |
| 193 | + |
| 194 | + def test_voice_detected(self): |
| 195 | + """Test that message with voice is detected.""" |
| 196 | + msg = MagicMock(spec=Message) |
| 197 | + msg.photo = None |
| 198 | + msg.video = None |
| 199 | + msg.animation = None |
| 200 | + msg.document = None |
| 201 | + msg.audio = None |
| 202 | + msg.voice = MagicMock() |
| 203 | + msg.video_note = None |
| 204 | + |
| 205 | + assert has_media(msg) is True |
| 206 | + |
| 207 | + def test_video_note_detected(self): |
| 208 | + """Test that message with video_note is detected.""" |
| 209 | + msg = MagicMock(spec=Message) |
| 210 | + msg.photo = None |
| 211 | + msg.video = None |
| 212 | + msg.animation = None |
| 213 | + msg.document = None |
| 214 | + msg.audio = None |
| 215 | + msg.voice = None |
| 216 | + msg.video_note = MagicMock() |
| 217 | + |
| 218 | + assert has_media(msg) is True |
| 219 | + |
| 220 | + def test_no_media_returns_false(self): |
| 221 | + """Test that message without media returns False.""" |
| 222 | + msg = MagicMock(spec=Message) |
| 223 | + msg.photo = None |
| 224 | + msg.video = None |
| 225 | + msg.animation = None |
| 226 | + msg.document = None |
| 227 | + msg.audio = None |
| 228 | + msg.voice = None |
| 229 | + msg.video_note = None |
| 230 | + |
| 231 | + assert has_media(msg) is False |
| 232 | + |
| 233 | + |
138 | 234 | class TestUrlWhitelist: |
139 | 235 | """Tests for URL whitelist functionality.""" |
140 | 236 |
|
@@ -305,14 +401,21 @@ def mock_update(self): |
305 | 401 | update.effective_chat = MagicMock(spec=Chat) |
306 | 402 | update.effective_chat.id = -100123456 # group_id from group_config |
307 | 403 |
|
308 | | - # Default: not forwarded, no links, no external reply, no story |
| 404 | + # Default: not forwarded, no links, no external reply, no story, no media |
309 | 405 | update.message.forward_origin = None |
310 | 406 | update.message.external_reply = None |
311 | 407 | update.message.story = None |
312 | 408 | update.message.entities = None |
313 | 409 | update.message.caption_entities = None |
314 | 410 | update.message.text = None |
315 | 411 | update.message.caption = None |
| 412 | + update.message.photo = None |
| 413 | + update.message.video = None |
| 414 | + update.message.animation = None |
| 415 | + update.message.document = None |
| 416 | + update.message.audio = None |
| 417 | + update.message.voice = None |
| 418 | + update.message.video_note = None |
316 | 419 | update.message.delete = AsyncMock() |
317 | 420 |
|
318 | 421 | return update |
@@ -687,6 +790,162 @@ async def test_deletes_message_with_story( |
687 | 790 |
|
688 | 791 | mock_update.message.delete.assert_called_once() |
689 | 792 |
|
| 793 | + @pytest.mark.asyncio |
| 794 | + async def test_deletes_message_with_media( |
| 795 | + self, mock_update, mock_context, group_config |
| 796 | + ): |
| 797 | + """Test that messages with media attachments are deleted.""" |
| 798 | + mock_update.message.photo = [MagicMock()] # Any non-None value |
| 799 | + |
| 800 | + mock_record = MagicMock() |
| 801 | + mock_record.joined_at = datetime.now(UTC) |
| 802 | + |
| 803 | + updated_record = MagicMock() |
| 804 | + updated_record.violation_count = 1 |
| 805 | + |
| 806 | + mock_db = MagicMock() |
| 807 | + mock_db.get_new_user_probation.return_value = mock_record |
| 808 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 809 | + |
| 810 | + with ( |
| 811 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 812 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 813 | + ): |
| 814 | + with pytest.raises(ApplicationHandlerStop): |
| 815 | + await handle_new_user_spam(mock_update, mock_context) |
| 816 | + |
| 817 | + mock_update.message.delete.assert_called_once() |
| 818 | + |
| 819 | + @pytest.mark.asyncio |
| 820 | + async def test_deletes_message_with_video( |
| 821 | + self, mock_update, mock_context, group_config |
| 822 | + ): |
| 823 | + """Test that messages with video are deleted.""" |
| 824 | + mock_update.message.video = MagicMock() |
| 825 | + |
| 826 | + mock_record = MagicMock() |
| 827 | + mock_record.joined_at = datetime.now(UTC) |
| 828 | + |
| 829 | + updated_record = MagicMock() |
| 830 | + updated_record.violation_count = 1 |
| 831 | + |
| 832 | + mock_db = MagicMock() |
| 833 | + mock_db.get_new_user_probation.return_value = mock_record |
| 834 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 835 | + |
| 836 | + with ( |
| 837 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 838 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 839 | + ): |
| 840 | + with pytest.raises(ApplicationHandlerStop): |
| 841 | + await handle_new_user_spam(mock_update, mock_context) |
| 842 | + |
| 843 | + mock_update.message.delete.assert_called_once() |
| 844 | + |
| 845 | + @pytest.mark.asyncio |
| 846 | + async def test_deletes_message_with_animation( |
| 847 | + self, mock_update, mock_context, group_config |
| 848 | + ): |
| 849 | + """Test that messages with animation are deleted.""" |
| 850 | + mock_update.message.animation = MagicMock() |
| 851 | + |
| 852 | + mock_record = MagicMock() |
| 853 | + mock_record.joined_at = datetime.now(UTC) |
| 854 | + |
| 855 | + updated_record = MagicMock() |
| 856 | + updated_record.violation_count = 1 |
| 857 | + |
| 858 | + mock_db = MagicMock() |
| 859 | + mock_db.get_new_user_probation.return_value = mock_record |
| 860 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 861 | + |
| 862 | + with ( |
| 863 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 864 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 865 | + ): |
| 866 | + with pytest.raises(ApplicationHandlerStop): |
| 867 | + await handle_new_user_spam(mock_update, mock_context) |
| 868 | + |
| 869 | + mock_update.message.delete.assert_called_once() |
| 870 | + |
| 871 | + @pytest.mark.asyncio |
| 872 | + async def test_deletes_message_with_audio( |
| 873 | + self, mock_update, mock_context, group_config |
| 874 | + ): |
| 875 | + """Test that messages with audio are deleted.""" |
| 876 | + mock_update.message.audio = MagicMock() |
| 877 | + |
| 878 | + mock_record = MagicMock() |
| 879 | + mock_record.joined_at = datetime.now(UTC) |
| 880 | + |
| 881 | + updated_record = MagicMock() |
| 882 | + updated_record.violation_count = 1 |
| 883 | + |
| 884 | + mock_db = MagicMock() |
| 885 | + mock_db.get_new_user_probation.return_value = mock_record |
| 886 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 887 | + |
| 888 | + with ( |
| 889 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 890 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 891 | + ): |
| 892 | + with pytest.raises(ApplicationHandlerStop): |
| 893 | + await handle_new_user_spam(mock_update, mock_context) |
| 894 | + |
| 895 | + mock_update.message.delete.assert_called_once() |
| 896 | + |
| 897 | + @pytest.mark.asyncio |
| 898 | + async def test_deletes_message_with_voice( |
| 899 | + self, mock_update, mock_context, group_config |
| 900 | + ): |
| 901 | + """Test that messages with voice are deleted.""" |
| 902 | + mock_update.message.voice = MagicMock() |
| 903 | + |
| 904 | + mock_record = MagicMock() |
| 905 | + mock_record.joined_at = datetime.now(UTC) |
| 906 | + |
| 907 | + updated_record = MagicMock() |
| 908 | + updated_record.violation_count = 1 |
| 909 | + |
| 910 | + mock_db = MagicMock() |
| 911 | + mock_db.get_new_user_probation.return_value = mock_record |
| 912 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 913 | + |
| 914 | + with ( |
| 915 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 916 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 917 | + ): |
| 918 | + with pytest.raises(ApplicationHandlerStop): |
| 919 | + await handle_new_user_spam(mock_update, mock_context) |
| 920 | + |
| 921 | + mock_update.message.delete.assert_called_once() |
| 922 | + |
| 923 | + @pytest.mark.asyncio |
| 924 | + async def test_deletes_message_with_video_note( |
| 925 | + self, mock_update, mock_context, group_config |
| 926 | + ): |
| 927 | + """Test that messages with video_note are deleted.""" |
| 928 | + mock_update.message.video_note = MagicMock() |
| 929 | + |
| 930 | + mock_record = MagicMock() |
| 931 | + mock_record.joined_at = datetime.now(UTC) |
| 932 | + |
| 933 | + updated_record = MagicMock() |
| 934 | + updated_record.violation_count = 1 |
| 935 | + |
| 936 | + mock_db = MagicMock() |
| 937 | + mock_db.get_new_user_probation.return_value = mock_record |
| 938 | + mock_db.increment_new_user_violation.return_value = updated_record |
| 939 | + |
| 940 | + with ( |
| 941 | + patch("bot.handlers.anti_spam.get_group_config_for_update", return_value=group_config), |
| 942 | + patch("bot.handlers.anti_spam.get_database", return_value=mock_db), |
| 943 | + ): |
| 944 | + with pytest.raises(ApplicationHandlerStop): |
| 945 | + await handle_new_user_spam(mock_update, mock_context) |
| 946 | + |
| 947 | + mock_update.message.delete.assert_called_once() |
| 948 | + |
690 | 949 | @pytest.mark.asyncio |
691 | 950 | async def test_ignores_update_without_message(self, mock_context): |
692 | 951 | """Test that update without message is ignored.""" |
|
0 commit comments