"""Tests for RSS feed generation.""" import xml.etree.ElementTree as ET from src.rss import generate_feed, strip_markdown, format_rfc822 class TestStripMarkdown: def test_plain_text(self): assert strip_markdown("Hello world") == "Hello world" def test_bold_and_lists(self): result = strip_markdown("**bold** and - item1\n- item2") assert "bold" in result assert "item1" in result def test_empty_string(self): assert strip_markdown("") == "" def test_none(self): assert strip_markdown("") == "" def test_code_blocks(self): result = strip_markdown("```python\nprint('hello')\n```") assert "print" in result or "hello" in result class TestFormatRfc822: def test_iso_to_rfc822(self): result = format_rfc822("2025-01-15T10:30:00Z") assert "Wed" in result or "15" in result assert "2025" in result def test_with_tz_offset(self): result = format_rfc822("2025-01-15T10:30:00+00:00") assert "2025" in result class TestGenerateFeed: def _entries(self, count=1): return [ { "tag_name": f"v1.{i}.0", "title": f"Release v1.{i}.0", "body": f"# Release notes\n\n- Feature {i}", "published_at": "2025-01-15T10:30:00Z", "html_url": f"https://github.com/owner/repo/releases/tag/v1.{i}.0", "owner_repo": "owner/repo", } for i in range(count) ] def test_basic_feed(self): entries = self._entries(2) xml_str = generate_feed(entries) root = ET.fromstring(xml_str) assert root.tag == "rss" assert root.get("version") == "2.0" channel = root[0] assert channel.tag == "channel" assert channel.find("title").text == "GitHub Release Monitor" assert channel.find("description").text == "Latest releases from tracked GitHub repositories" items = channel.findall("item") assert len(items) == 2 def test_item_structure(self): entries = self._entries(1) xml_str = generate_feed(entries) root = ET.fromstring(xml_str) channel = root[0] item = channel.find("item") assert item.find("title").text == "[owner/repo] Release v1.0.0" assert "Feature 0" in item.find("description").text assert item.find("link").text == "https://github.com/owner/repo/releases/tag/v1.0.0" assert item.find("pubDate").text is not None assert item.find("guid").text == "github.com/owner/repo/releases/tag/v1.0.0" def test_description_truncation(self): entry = { "tag_name": "v1.0.0", "title": "Release", "body": "A" * 400, "published_at": "2025-01-15T10:30:00Z", "html_url": "https://example.com", "owner_repo": "owner/repo", } xml_str = generate_feed([entry]) root = ET.fromstring(xml_str) desc = root[0].find("item").find("description").text assert len(desc) == 301 # 300 chars + 1 ellipsis char def test_tag_entry_no_body(self): entry = { "tag_name": "v1.0.0", "title": "v1.0.0", "body": "", "published_at": "2025-01-15T10:30:00Z", "html_url": "https://github.com/owner/repo/tags/v1.0.0", "owner_repo": "owner/repo", } xml_str = generate_feed([entry]) root = ET.fromstring(xml_str) desc = root[0].find("item").find("description").text assert desc == "Tag v1.0.0" def test_item_cap(self): entries = self._entries(100) xml_str = generate_feed(entries) root = ET.fromstring(xml_str) items = root[0].findall("item") assert len(items) == 50 # RSS_MAX_ITEMS def test_xml_escaping(self): entry = { "tag_name": "v1.0.0", "title": "Release with & 'chars'", "body": "Some **bold** text", "published_at": "2025-01-15T10:30:00Z", "html_url": "https://example.com?a=1&b=2", "owner_repo": "owner/repo", } xml_str = generate_feed([entry]) # Should parse without error root = ET.fromstring(xml_str) assert root is not None def test_base_url(self): entries = self._entries(1) xml_str = generate_feed(entries, base_url="http://example.com:9000") root = ET.fromstring(xml_str) assert root[0].find("link").text == "http://example.com:9000/" def test_media_content_with_image(self): entries = self._entries(1) entries[0]["image_url"] = "https://example.com/logo.png" xml_str = generate_feed(entries) root = ET.fromstring(xml_str) assert root is not None media_el = root[0].find("item").find("{http://search.yahoo.com/mrss/}content") assert media_el is not None assert media_el.get("url") == "https://example.com/logo.png" assert media_el.get("medium") == "image" assert "media" in xml_str or "search.yahoo.com/mrss" in xml_str def test_no_media_content_without_image(self): entries = self._entries(1) entries[0]["image_url"] = None xml_str = generate_feed(entries) root = ET.fromstring(xml_str) media_el = root[0].find("item").find("{http://search.yahoo.com/mrss/}content") assert media_el is None def test_no_media_content_when_key_missing(self): entries = self._entries(1) entries[0]["image_url"] = None xml_str = generate_feed(entries) root = ET.fromstring(xml_str) media_el = root[0].find("item").find("{http://search.yahoo.com/mrss/}content") assert media_el is None def test_media_content_multiple_items_selective(self): entries = self._entries(2) entries[0]["image_url"] = "https://example.com/logo1.png" entries[1]["image_url"] = None xml_str = generate_feed(entries) root = ET.fromstring(xml_str) items = root[0].findall("item") media_1 = items[0].find("{http://search.yahoo.com/mrss/}content") media_2 = items[1].find("{http://search.yahoo.com/mrss/}content") assert media_1 is not None assert media_1.get("url") == "https://example.com/logo1.png" assert media_2 is None def test_image_with_special_chars_in_url(self): entries = self._entries(1) entries[0]["image_url"] = "https://example.com/logo.png?w=200&h=200" xml_str = generate_feed(entries) root = ET.fromstring(xml_str) assert root is not None media_el = root[0].find("item").find("{http://search.yahoo.com/mrss/}content") assert "w=200" in media_el.get("url")