"""RSS/Atom parsing, including the RDF format the old system could not read.""" import httpx import pytest from news.sources import rss RDF = """ Thttp://x d German economy frets about election result http://x/1?utm_source=rss Some <b>markup</b> here """ ATOM = """ F Release v1.2.3 2026-09-08T12:00:00Z """ def _source(): return {"id": "s1", "url": "http://feed", "kind": "rss"} async def _fetch(body: str, status: int = 200, headers: dict | None = None): def handler(request): return httpx.Response(status, content=body.encode(), headers=headers or {}) transport = httpx.MockTransport(handler) async with httpx.AsyncClient(transport=transport) as client: return await rss.fetch(client, _source()) @pytest.mark.asyncio async def test_parses_rss_1_rdf(): """DW ships RDF; the old hand-rolled parser yielded zero items silently.""" items, _ = await _fetch(RDF) assert len(items) == 1 assert items[0]["title"].startswith("German economy") # tracking parameter stripped, markup removed from the summary assert items[0]["canonical_url"] == "http://x/1" assert "" not in items[0]["summary"] and "markup" in items[0]["summary"] @pytest.mark.asyncio async def test_atom_prefers_alternate_link_over_self(): items, _ = await _fetch(ATOM) assert len(items) == 1 assert items[0]["canonical_url"] == "http://x/rel" @pytest.mark.asyncio async def test_304_returns_no_items_and_is_not_an_error(): items, state = await _fetch("", status=304) assert items == [] and state["status"] == "304" @pytest.mark.asyncio async def test_429_is_reported_as_rate_limited_not_raised(): """Reddit 429s readily; that must back off, not raise every cycle.""" items, state = await _fetch("", status=429, headers={"retry-after": "120"}) assert items == [] assert state["rate_limited"] is True assert state["retry_after"] == "120" @pytest.mark.asyncio async def test_server_error_still_raises(): with pytest.raises(httpx.HTTPStatusError): await _fetch("", status=503)