from __future__ import annotations import httpx import pytest import respx from app.collectors.base import CompanyContext, SourceConfig from app.collectors.rss import RssCollector from app.models.enums import SourceStatus, SourceType FEED_XML = """ Acme News Acme opens new facility https://example.com/news/1 Acme Corp opened a new manufacturing facility this week. Mon, 01 Jun 2026 10:00:00 GMT Acme hires new VP https://example.com/news/2 Acme Corp announced a new VP of Engineering. """ COMPANY = CompanyContext(id="c1", name="Acme Corp", official_website=None, monitoring_focus=None) @pytest.mark.asyncio async def test_discover_builds_a_google_news_search_url(): discovered = await RssCollector().discover(COMPANY) assert len(discovered) == 1 assert discovered[0].source_type == SourceType.RSS assert discovered[0].base_url.startswith("https://news.google.com/rss/search?q=") assert "Acme%20Corp" in discovered[0].base_url @pytest.mark.asyncio async def test_discover_url_encodes_ampersands_in_the_name(): company = CompanyContext(id="c1", name="AT&T", official_website=None, monitoring_focus=None) discovered = await RssCollector().discover(company) # A raw "&" from the company name must not leak into the query string, # since it would be parsed as a new URL parameter separator (splitting # the query in two) rather than part of the search text. query_part = discovered[0].base_url.split("q=", 1)[1].split("&hl=", 1)[0] assert "&" not in query_part @pytest.mark.asyncio async def test_collect_parses_feed_items(): source = SourceConfig( id="s1", source_type=SourceType.RSS, name="Acme RSS", base_url="https://example.com/feed" ) with respx.mock: respx.get("https://example.com/feed").mock(return_value=httpx.Response(200, text=FEED_XML)) result = await RssCollector().collect(source, COMPANY) assert result.status == SourceStatus.ACTIVE assert len(result.documents) == 2 assert result.documents[0].title == "Acme opens new facility" assert result.documents[0].publication_date is not None @pytest.mark.asyncio async def test_collect_fails_gracefully_on_http_error(): source = SourceConfig( id="s1", source_type=SourceType.RSS, name="Acme RSS", base_url="https://example.com/feed" ) with respx.mock: respx.get("https://example.com/feed").mock(return_value=httpx.Response(500)) result = await RssCollector().collect(source, COMPANY) assert result.status == SourceStatus.FAILED assert result.documents == [] @pytest.mark.asyncio async def test_collect_without_base_url_fails_without_network_call(): source = SourceConfig(id="s1", source_type=SourceType.RSS, name="Acme RSS", base_url=None) result = await RssCollector().collect(source, COMPANY) assert result.status == SourceStatus.FAILED