import os import tempfile import unittest from datetime import datetime, timezone from unittest.mock import patch, MagicMock from services.feeds import looks_like_feed, parse_feed from services.sources import get_sources from services.headlines import prepare_headlines from structs.headline import Headline RSS_2_0 = """ Test feed First news storyhttps://x/1Mon, 14 Sep 2026 12:00:00 GMT Second news storyhttps://x/2 """ ATOM = """ Atom story one2026-09-14T14:30:00Z Atom story two2026-09-13T10:00:00Z """ class TestLooksLikeFeed(unittest.TestCase): def test_rss_and_atom_detected(self): self.assertTrue(looks_like_feed(RSS_2_0)) self.assertTrue(looks_like_feed(ATOM)) def test_html_not_detected(self): self.assertFalse(looks_like_feed("Story")) def test_empty_not_detected(self): self.assertFalse(looks_like_feed("")) self.assertFalse(looks_like_feed(None)) class TestParseFeed(unittest.TestCase): def test_rss_2_0(self): items = parse_feed(RSS_2_0) titles = [t for t, _ in items] self.assertEqual(titles, ["First news story", "Second news story"]) self.assertEqual(items[0][1].year, 2026) self.assertIsNone(items[1][1]) def test_atom(self): items = parse_feed(ATOM) titles = [t for t, _ in items] self.assertEqual(titles, ["Atom story one", "Atom story two"]) self.assertIsNotNone(items[0][1]) self.assertIsNotNone(items[1][1]) def test_malformed_returns_empty(self): self.assertEqual(parse_feed("not xml at all <<<"), []) class TestGetSources(unittest.TestCase): def test_skips_comments_and_blanks(self): path = None try: with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as f: f.write("# a comment\n\nhttps://a.com\n\nhttps://b.com\n# another\n") path = f.name self.assertEqual(get_sources(path), ["https://a.com", "https://b.com"]) finally: if path: os.unlink(path) class TestFeedDomainNormalization(unittest.TestCase): def test_feed_subdomain_stripped(self): self.assertEqual(Headline("a", ["a"], "https://feeds.npr.org/1001/rss.xml").domain, "npr.org") self.assertEqual(Headline("a", ["a"], "https://rss.nytimes.com/x").domain, "nytimes.com") self.assertEqual(Headline("a", ["a"], "https://www.vox.com/rss/index.xml").domain, "vox.com") class TestPrepareHeadlinesFeed(unittest.TestCase): def setUp(self): self.stopwords = {"the", "a", "an", "in", "on", "and", "of", "to"} @patch("services.headlines.requests.get") def test_feed_source_is_parsed(self, mock_get): mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = RSS_2_0.encode("utf-8") mock_response.text = RSS_2_0 mock_get.return_value = mock_response headlines = prepare_headlines(["https://feeds.bbci.co.uk/news/world/rss.xml"], self.stopwords) titles = [h.display_text for h in headlines] self.assertEqual(titles, ["First news story", "Second news story"]) # Domain comes from the feed host, normalized to the outlet. self.assertEqual(headlines[0].domain, "bbci.co.uk") self.assertIsNotNone(headlines[0].published_at) if __name__ == "__main__": unittest.main()