import pytest from app.library.task_handlers.rss import RssGenericHandler from app.library.Tasks import Task, TaskResult class DummyResponse: def __init__(self, text: str): self.text = text def raise_for_status(self) -> None: return None class DummyOpts: def __init__(self, data): self._data = data def get_all(self): return self._data class TestRssHandlerParsing: """Test URL parsing for RSS/Atom feeds using the tests() method.""" @pytest.mark.parametrize(("url", "expected"), RssGenericHandler.tests()) def test_url_parsing(self, url: str, expected: bool): """Test URL parsing against all defined test cases.""" result = RssGenericHandler.parse(url) is_matched = result is not None assert is_matched == expected, f"URL '{url}' expected {expected}, got {is_matched}" @pytest.mark.parametrize(("url", "expected"), RssGenericHandler.tests()) def test_returns_url_dict_on_match(self, url: str, expected: bool): """Test that parse returns dict with 'url' key for valid feeds.""" result = RssGenericHandler.parse(url) if expected: assert result is not None assert "url" in result assert result["url"] == url else: assert result is None class TestRssHandlerExtraction: """Test RSS feed extraction and parsing.""" @pytest.mark.asyncio async def test_rss_atom_feed_extraction(self, monkeypatch): """Test extraction from Atom feed.""" atom_feed = """ Example Feed Video 1 2024-01-01T00:00:00Z Video 2 2024-01-02T00:00:00Z """.strip() async def fake_request(**kwargs): # noqa: ARG001 return DummyResponse(atom_feed) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_rss", name="Test Atom Feed", url="https://example.com/feed.atom", preset="default", ) result = await RssGenericHandler.extract(task) assert isinstance(result, TaskResult) assert len(result.items) == 2 assert result.items[0].title == "Video 1" assert result.items[0].url == "https://www.youtube.com/watch?v=abc123" assert result.items[1].title == "Video 2" assert result.items[1].url == "https://www.youtube.com/watch?v=def456" assert result.metadata["entry_count"] == 2 @pytest.mark.asyncio async def test_rss_feed_extraction(self, monkeypatch): """Test extraction from RSS feed.""" rss_feed = """ Example Channel Video 1 https://www.youtube.com/watch?v=abc123 Mon, 01 Jan 2024 00:00:00 +0000 Video 2 https://www.youtube.com/watch?v=def456 Tue, 02 Jan 2024 00:00:00 +0000 """.strip() async def fake_request(**kwargs): # noqa: ARG001 return DummyResponse(rss_feed) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_rss", name="Test RSS Feed", url="https://example.com/feed.rss", preset="default", ) result = await RssGenericHandler.extract(task) assert isinstance(result, TaskResult) assert len(result.items) == 2 assert result.items[0].title == "Video 1" assert result.items[0].url == "https://www.youtube.com/watch?v=abc123" assert result.items[1].title == "Video 2" assert result.items[1].url == "https://www.youtube.com/watch?v=def456" assert result.metadata["entry_count"] == 2 @pytest.mark.asyncio async def test_can_handle(self, monkeypatch): """Test can_handle method.""" atom_feed = """ """.strip() async def fake_request(**kwargs): # noqa: ARG001 return DummyResponse(atom_feed) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_rss", name="Test Feed", url="https://example.com/feed.atom", preset="default", ) assert RssGenericHandler.can_handle(task) is True non_feed_task = Task( id="test_youtube", name="YouTube Video", url="https://www.youtube.com/watch?v=abc123", preset="default", ) assert RssGenericHandler.can_handle(non_feed_task) is False class TestRssHandlerEdgeCases: """Test edge cases in RSS handling.""" @pytest.mark.asyncio async def test_empty_feed(self, monkeypatch): """Test handling of empty feed.""" empty_feed = """ Empty Channel """.strip() async def fake_request(**kwargs): # noqa: ARG001 return DummyResponse(empty_feed) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_empty", name="Empty Feed", url="https://example.com/feed.rss", preset="default", ) result = await RssGenericHandler.extract(task) assert isinstance(result, TaskResult) assert len(result.items) == 0 assert result.metadata["entry_count"] == 0 @pytest.mark.asyncio async def test_invalid_feed_url(self, monkeypatch): """Test handling of invalid feed URL.""" from app.library.Tasks import TaskFailure async def fake_request(**kwargs): # noqa: ARG001 msg = "Network error" raise Exception(msg) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_invalid", name="Invalid Feed", url="https://example.com/feed.rss", preset="default", ) result = await RssGenericHandler.extract(task) assert isinstance(result, TaskFailure) @pytest.mark.asyncio async def test_missing_urls_in_feed(self, monkeypatch): """Test handling of entries missing URLs.""" feed = """ No URL Item Valid Item https://www.youtube.com/watch?v=abc123 """.strip() async def fake_request(**kwargs): # noqa: ARG001 return DummyResponse(feed) monkeypatch.setattr(RssGenericHandler, "request", staticmethod(fake_request)) monkeypatch.setattr(Task, "get_ytdlp_opts", lambda self: DummyOpts({"download_archive": "/tmp/archive"})) # noqa: ARG005 task = Task( id="test_missing", name="Feed with Missing URLs", url="https://example.com/feed.rss", preset="default", ) result = await RssGenericHandler.extract(task) # Should only include the item with URL assert isinstance(result, TaskResult) assert len(result.items) == 1 assert result.items[0].title == "Valid Item"