"""Unit coverage for SPEC-013 news digest (NEWS-01..03).""" import unittest from unittest.mock import AsyncMock from fjerkroa_bot.news import NewsFetcher, parse_feed, render_digest RSS = b""" Game X releasedhttps://ex.com/x Patch Y noteshttps://ex.com/y """ ATOM = b""" Atom headline """ class TestParse(unittest.TestCase): def test_rss(self): """NEWS-01: RSS items parsed with title + link.""" items = parse_feed(RSS, "Src") self.assertEqual([i["title"] for i in items], ["Game X released", "Patch Y notes"]) self.assertEqual(items[0]["link"], "https://ex.com/x") self.assertEqual(items[0]["source"], "Src") def test_atom(self): """NEWS-01: Atom entries parsed with href link.""" items = parse_feed(ATOM, "A") self.assertEqual(items[0]["title"], "Atom headline") self.assertEqual(items[0]["link"], "https://ex.com/a") def test_malformed_never_raises(self): """NEWS-01: garbage XML returns [] without raising.""" self.assertEqual(parse_feed(b" dedup fetcher = NewsFetcher(guard, fetch) feeds = [ ("https://a.com/feed", "A"), ("https://internal/feed", "Internal"), # SSRF-skipped ("https://boom.com/feed", "Boom"), # fetch fails ("https://b.com/feed", "B"), # same RSS -> dup titles dropped ] items = await fetcher.collect(feeds, per_feed=5) titles = [i["title"] for i in items] self.assertEqual(titles, ["Game X released", "Patch Y notes"]) # deduped, internal+boom skipped async def test_per_feed_limit(self): """NEWS-03: per-feed cap honored.""" fetcher = NewsFetcher(lambda u: None, AsyncMock(return_value=RSS)) items = await fetcher.collect([("https://a.com", "A")], per_feed=1) self.assertEqual(len(items), 1)