# pylint: disable=protected-access, wrong-import-position import unittest from unittest.mock import MagicMock, PropertyMock import sys import os # Add project root to sys.path to find post_parser sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Mock the config module before importing PostParser sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings']) from pyrogram.types import Message, Chat, Reaction, MessageReactions from pyrogram.enums import MessageMediaType from post_parser import PostParser # Import after mocking config class TestPostParserExtractFlags(unittest.TestCase): def setUp(self): self.client_mock = MagicMock() self.parser = PostParser(self.client_mock) # Mock the get_channel_username method to return a default channel name self.parser.get_channel_username = MagicMock(return_value="test_channel") def tearDown(self): # Reset mocks if needed pass def _create_mock_message(self, media=None, text=None, caption=None, reactions_data=None, # Format: [("emoji", count), ...] forward_origin=None, # Pass forward info via this chat_username="test_channel"): # Default chat username message = MagicMock(spec=Message) message.media = media message.id = 123 # Add a default message ID message.forward_origin = None # Add missing attribute message.web_page = None # Add missing attribute checked in _extract_flags # Mock chat attribute mock_chat = MagicMock(spec=Chat) mock_chat.username = chat_username mock_chat.id = -1001234567890 if chat_username.startswith('-100') else 1234567890 message.chat = mock_chat # Mock reactions if reactions_data: mock_reactions_list = [] for emoji, count in reactions_data: reaction_mock = MagicMock(spec=Reaction) reaction_mock.emoji = emoji reaction_mock.count = count mock_reactions_list.append(reaction_mock) reactions_container_mock = MagicMock(spec=MessageReactions) reactions_container_mock.reactions = mock_reactions_list message.reactions = reactions_container_mock else: message.reactions = None # Mock forwarding info using the new forward_origin attribute if forward_origin: message.forward_origin = MagicMock() if forward_origin is True else forward_origin else: message.forward_origin = None # Correctly mock text and caption to have an .html attribute and behave like strings if text: mock_text = MagicMock() mock_text.html = text mock_text.__str__.return_value = text # Add __str__ mock message.text = mock_text message.caption = None elif caption: mock_caption = MagicMock() mock_caption.html = caption mock_caption.__str__.return_value = caption # Add __str__ mock message.caption = mock_caption message.text = None else: message.text = None message.caption = None # Mock other necessary attributes accessed by _generate_html_body and other helpers message.poll = None message.channel_chat_created = False message.reply_to_message = None message.service = None message.web_page = None # Assume no web page unless specifically tested for flags return message # --- Test Cases --- def test_flag_fwd_forwarded_from_chat(self): message = self._create_mock_message(forward_origin=MagicMock(sender_chat=True)) self.assertIn("fwd", self.parser._extract_flags(message)) def test_flag_fwd_forwarded_from_user(self): message = self._create_mock_message(forward_origin=MagicMock(sender_user=True)) self.assertIn("fwd", self.parser._extract_flags(message)) def test_flag_fwd_forwarded_sender_name(self): message = self._create_mock_message(forward_origin=MagicMock(sender_user_name="Hidden Sender")) self.assertIn("fwd", self.parser._extract_flags(message)) def test_flag_video_media_video_short_caption(self): message = self._create_mock_message(media=MessageMediaType.VIDEO, caption="Short video") self.assertIn("video", self.parser._extract_flags(message)) def test_flag_video_media_video_note_short_text(self): message = self._create_mock_message(media=MessageMediaType.VIDEO_NOTE, text="Short note") self.assertIn("video", self.parser._extract_flags(message)) def test_flag_video_media_animation_no_text(self): message = self._create_mock_message(media=MessageMediaType.ANIMATION) self.assertIn("video", self.parser._extract_flags(message)) def test_flag_video_media_video_long_caption(self): long_caption = "This is a very long caption that exceeds the two hundred character limit, therefore it should not trigger the video flag even though the media type is video." * 5 # Create a simple mock message for this specific test message = MagicMock(spec=Message) message.media = MessageMediaType.VIDEO message.id = 123 message.forward_origin = None # Add missing attribute message.web_page = None # Add missing attribute checked in _extract_flags # Create a basic mock Chat mock_chat = MagicMock(spec=Chat) mock_chat.username = "test_channel" mock_chat.id = 1234567890 message.chat = mock_chat # Set up text and caption to ensure len() returns the correct length message.text = None # Create a custom class that mimics caption with html attribute class CaptionWithHtml(str): @property def html(self): return self # Use our class instead of a regular string message.caption = CaptionWithHtml(long_caption) # Mock HTML body generation self.parser._generate_html_body = MagicMock(return_value=long_caption) # In _extract_flags, text length is checked and should be > 200 self.assertGreater(len(message.caption), 200) self.assertNotIn("video", self.parser._extract_flags(message)) def test_flag_no_image_no_media(self): message = self._create_mock_message(text="Just text") self.assertIn("no_image", self.parser._extract_flags(message)) def test_flag_no_image_poll_media(self): # Need to mock poll structure if _generate_html_body uses it, but _extract_flags doesn't directly mock_poll = MagicMock() mock_poll.question = "A poll?" message = self._create_mock_message(media=MessageMediaType.POLL) message.poll = mock_poll # Assign mock poll self.assertIn("no_image", self.parser._extract_flags(message)) def test_flag_no_image_with_image_media(self): message = self._create_mock_message(media=MessageMediaType.PHOTO) self.assertNotIn("no_image", self.parser._extract_flags(message)) def test_flag_sticker(self): message = self._create_mock_message(media=MessageMediaType.STICKER) self.assertIn("sticker", self.parser._extract_flags(message)) def test_flag_stream_keyword_stream(self): message = self._create_mock_message(text="Join our стрим tonight!") self.assertIn("stream", self.parser._extract_flags(message)) def test_flag_stream_keyword_webinar(self): message = self._create_mock_message(text="Register for the вебинар.") self.assertIn("stream", self.parser._extract_flags(message)) def test_flag_stream_keyword_lecture(self): message = self._create_mock_message(text="Upcoming онлайн-лекция about AI.") self.assertIn("stream", self.parser._extract_flags(message)) def test_flag_stream_keyword_livestream(self): message = self._create_mock_message(text="Watch the livestream now.") self.assertIn("stream", self.parser._extract_flags(message)) def test_flag_stream_case_insensitive(self): message = self._create_mock_message(text="Watch the LiveStream now.") self.assertIn("stream", self.parser._extract_flags(message)) def test_flag_donat(self): message = self._create_mock_message(text="Support us via донат.") self.assertIn("donat", self.parser._extract_flags(message)) def test_flag_donat_case_insensitive(self): message = self._create_mock_message(text="Support us via ДОНАТ.") self.assertIn("donat", self.parser._extract_flags(message)) def test_flag_donat_with_boost_link(self): """Test that t.me/boost/ links add donat flag.""" message = self._create_mock_message(text="Support the channel: https://t.me/boost/channel_name") self.assertIn("donat", self.parser._extract_flags(message)) def test_flag_donat_with_cloudtips_link(self): """Test that pay.cloudtips.ru links add donat flag.""" message = self._create_mock_message(text="Support us via this payment link: https://pay.cloudtips.ru/some_id") self.assertIn("donat", self.parser._extract_flags(message)) def test_flag_donat_with_cloudtips_link_case_insensitive(self): """Test that PAY.CLOUDTIPS.RU links add donat flag (case insensitive).""" message = self._create_mock_message(text="Support us via PAY.CLOUDTIPS.RU/user") self.assertIn("donat", self.parser._extract_flags(message)) def test_flag_clown_reaction(self): message = self._create_mock_message(reactions_data=[("🤡", 35), ("👍", 10)]) self.assertIn("clownpoo", self.parser._extract_flags(message)) def test_flag_clown_reaction_not_enough(self): message = self._create_mock_message(reactions_data=[("🤡", 29), ("👍", 10)]) self.assertNotIn("clownpoo", self.parser._extract_flags(message)) def test_flag_poo_reaction(self): message = self._create_mock_message(reactions_data=[("💩", 30), ("👎", 5)]) self.assertIn("clownpoo", self.parser._extract_flags(message)) def test_flag_poo_reaction_not_enough(self): message = self._create_mock_message(reactions_data=[("💩", 15), ("👎", 5)]) self.assertNotIn("clownpoo", self.parser._extract_flags(message)) def test_flag_advert_hashtag(self): message = self._create_mock_message(text="Check this out #реклама") self.assertIn("advert", self.parser._extract_flags(message)) def test_flag_advert_partner_post(self): message = self._create_mock_message(text="Партнерский пост о новом продукте.") self.assertIn("advert", self.parser._extract_flags(message)) def test_flag_advert_promo_code(self): message = self._create_mock_message(text="Use code XYZ по промокоду!") self.assertIn("advert", self.parser._extract_flags(message)) def test_flag_advert_erid(self): message = self._create_mock_message(text="Some text erid:laskdjfhasdlkjfh") self.assertIn("advert", self.parser._extract_flags(message)) def test_flag_paywall_boosty(self): message = self._create_mock_message(text="Full version on Boosty!") self.assertIn("paywall", self.parser._extract_flags(message)) def test_flag_paywall_sponsr(self): message = self._create_mock_message(text="Support me on Sponsr.") self.assertIn("paywall", self.parser._extract_flags(message)) def test_flag_link_in_text(self): message = self._create_mock_message(text="Visit https://example.com") # Use _generate_html_body to ensure link detection logic runs _ = self.parser._generate_html_body(message) # Call body generation which prepares text self.assertIn("link", self.parser._extract_flags(message)) def test_flag_only_link_in_text(self): message = self._create_mock_message(text="https://example.com") # Use _generate_html_body to ensure link detection logic runs _ = self.parser._generate_html_body(message) # Call body generation which prepares text self.assertIn("only_link", self.parser._extract_flags(message)) def test_flag_only_webpage(self): # Create a message with no text/caption first message = self._create_mock_message() # Manually set the web_page attribute to simulate a webpage preview message.web_page = MagicMock() # Use MagicMock to simulate presence # Ensure text and caption are None for the 'only_link' logic message.text = None message.caption = None # The flag logic should now correctly identify this as only_link self.assertIn("only_link", self.parser._extract_flags(message)) def test_flag_link_in_href(self): # Need to mock html property correctly message = self._create_mock_message(text="Check this link") type(message).html = PropertyMock(return_value="Check this link") # Use _generate_html_body to ensure link detection logic runs _ = self.parser._generate_html_body(message) self.assertIn("link", self.parser._extract_flags(message)) def test_flag_link_no_link(self): message = self._create_mock_message(text="Just plain text.") self.assertNotIn("link", self.parser._extract_flags(message)) def test_flag_mention(self): message = self._create_mock_message(text="Mentioning @some_channel here.") self.assertIn("mention", self.parser._extract_flags(message)) def test_flag_mention_no_mention(self): message = self._create_mock_message(text="No mentions here.") self.assertNotIn("mention", self.parser._extract_flags(message)) def test_flag_hid_channel(self): message = self._create_mock_message(text="Join here: https://t.me/+ABC123xyz") self.assertIn("hid_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_different_channel(self): message = self._create_mock_message(text="Check out https://t.me/another_channel", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_same_channel(self): message = self._create_mock_message(text="Our main channel https://t.me/test_channel", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_case_insensitive(self): message = self._create_mock_message(text="Our main channel https://t.me/TEST_channel", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_no_current_channel(self): message = self._create_mock_message(text="Link to https://t.me/another_channel") self.parser.get_channel_username = MagicMock(return_value=None) # Simulate no current channel found self.assertIn("foreign_channel", self.parser._extract_flags(message)) # Should still flag as foreign if current unknown def test_flag_foreign_channel_boost_other_channel(self): """Test that boost link to foreign channel is flagged.""" message = self._create_mock_message(text="Boost another channel: https://t.me/boost/other_channel", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_boost_same_channel(self): """Test that boost link to own channel is not flagged.""" message = self._create_mock_message(text="Boost our channel: https://t.me/boost/test_channel", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_boost_link_case_insensitive(self): """Test that boost link to own channel is not flagged (case insensitive).""" message = self._create_mock_message(text="Boost our channel: https://t.me/boost/TEST_CHANNEL", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_multiple_links(self): """Test with multiple links - should flag if any is foreign.""" message = self._create_mock_message( text="Links: https://t.me/test_channel and https://t.me/other_channel and https://t.me/boost/test_channel", chat_username="test_channel" ) self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_only_boost_word(self): """Test that the word 'boost' is not flagged.""" message = self._create_mock_message(text="Check out https://t.me/boost", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_foreign_channel_boost_no_channel(self): """Test with boost/ but no channel after - should be flagged as foreign.""" message = self._create_mock_message(text="Strange link: https://t.me/boost/", chat_username="test_channel") self.parser.get_channel_username = MagicMock(return_value="test_channel") # Ensure current channel is mocked self.assertNotIn("foreign_channel", self.parser._extract_flags(message)) def test_flag_multiple_flags(self): message = self._create_mock_message( media=MessageMediaType.VIDEO, caption="Livestream announcement! Support via донат at https://example.com. Join https://t.me/+SECRET and mention @admin. #реклама", reactions_data=[("🤡", 40)], forward_origin=MagicMock(sender_chat=True) # Use forward_origin ) # Mock HTML generation as it's used internally _ = self.parser._generate_html_body(message) flags = self.parser._extract_flags(message) expected_flags = ["fwd", "video", "stream", "donat", "clownpoo", "advert", "link", "mention", "hid_channel"] self.assertCountEqual(flags, expected_flags) # Use assertCountEqual for order-insensitive list comparison def test_flag_no_flags(self): message = self._create_mock_message(media=MessageMediaType.PHOTO, caption="A simple photo post.") flags = self.parser._extract_flags(message) self.assertEqual(flags, []) # Expect an empty list # --- Additional Multiple Flags Tests --- def test_flag_multiple_fwd_link_mention(self): message = self._create_mock_message( text="Forwarded message with a link https://example.com and mention @someone.", forward_origin=MagicMock(sender_user_name="Hidden Sender") # Use forward_origin ) flags = self.parser._extract_flags(message) expected_flags = ["fwd", "no_image", "link", "mention"] self.assertCountEqual(flags, expected_flags) def test_flag_multiple_video_advert_clown(self): message = self._create_mock_message( media=MessageMediaType.VIDEO, caption="Short video #реклама", reactions_data=[("🤡", 50)] ) flags = self.parser._extract_flags(message) expected_flags = ["video", "advert", "clownpoo"] self.assertCountEqual(flags, expected_flags) def test_flag_multiple_no_image_stream_paywall_foreign(self): # Ensure current channel is mocked for foreign channel check self.parser.get_channel_username = MagicMock(return_value="current_channel") message = self._create_mock_message( text="Join our livestream! Full access on Boosty. Check out https://t.me/other_channel", ) flags = self.parser._extract_flags(message) expected_flags = ["no_image", "stream", "paywall", "foreign_channel"] self.assertCountEqual(flags, expected_flags) def test_flag_multiple_sticker_donat_poo_hid(self): message = self._create_mock_message( media=MessageMediaType.STICKER, caption="Sticker with донат link https://t.me/+SECRET", reactions_data=[("💩", 30)] ) flags = self.parser._extract_flags(message) # Sticker implies no 'no_image' flag expected_flags = ["sticker", "donat", "clownpoo", "hid_channel"] self.assertCountEqual(flags, expected_flags) def test_flag_multiple_text_only_advert_stream_clown_foreign(self): self.parser.get_channel_username = MagicMock(return_value="my_channel") message = self._create_mock_message( text="Это партнерский пост про наш вебинар. Ссылка на канал https://t.me/another. Регистрируйтесь тут!", reactions_data=[("🤡", 31)] ) flags = self.parser._extract_flags(message) expected_flags = ["no_image", "advert", "stream", "clownpoo", "foreign_channel"] self.assertCountEqual(flags, expected_flags) def test_flag_multiple_video_note_fwd_paywall_mention(self): message = self._create_mock_message( media=MessageMediaType.VIDEO_NOTE, caption="Video note from @someone, full version on Sponsr", forward_origin=MagicMock(sender_user=True) # Use forward_origin ) flags = self.parser._extract_flags(message) expected_flags = ["video", "fwd", "paywall", "mention"] self.assertCountEqual(flags, expected_flags) def test_flag_link_with_tme_excluded(self): message = self._create_mock_message(text="Check out this Telegram link https://t.me/channel_name") # Use _generate_html_body to ensure link detection logic runs _ = self.parser._generate_html_body(message) self.assertNotIn("link", self.parser._extract_flags(message)) def test_flag_link_with_mixed_links(self): message = self._create_mock_message(text="Check both links: https://example.com and https://t.me/channel_name") # Use _generate_html_body to ensure link detection logic runs _ = self.parser._generate_html_body(message) self.assertIn("link", self.parser._extract_flags(message)) if __name__ == '__main__': unittest.main()