Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

155 rader
6.2 KiB

  1. import unittest
  2. from unittest.mock import patch, MagicMock
  3. import requests
  4. from services.headlines import prepare_headlines, is_headline, DEFAULT_TIMEOUT, DEFAULT_HEADERS
  5. class TestHeadlinesTimeout(unittest.TestCase):
  6. def setUp(self):
  7. self.stopwords = {"the", "a", "an", "in", "on"}
  8. @patch("services.headlines.requests.get")
  9. def test_default_timeout_used(self, mock_get):
  10. mock_response = MagicMock()
  11. mock_response.status_code = 200
  12. mock_response.content = b"<html><body><a href='#'>Default Timeout Headline</a></body></html>"
  13. mock_response.text = "<html><body><a href='#'>Default Timeout Headline</a></body></html>"
  14. mock_get.return_value = mock_response
  15. sources = ["https://example.com/news"]
  16. headlines = prepare_headlines(sources, self.stopwords)
  17. mock_get.assert_called_once_with("https://example.com/news", allow_redirects=True, timeout=DEFAULT_TIMEOUT, headers=DEFAULT_HEADERS)
  18. self.assertEqual(len(headlines), 1)
  19. self.assertEqual(headlines[0].display_text, "Default Timeout Headline")
  20. @patch("services.headlines.requests.get")
  21. def test_custom_timeout_used(self, mock_get):
  22. mock_response = MagicMock()
  23. mock_response.status_code = 200
  24. mock_response.content = b"<html><body><a href='#'>Custom Timeout Headline</a></body></html>"
  25. mock_response.text = "<html><body><a href='#'>Custom Timeout Headline</a></body></html>"
  26. mock_get.return_value = mock_response
  27. sources = ["https://example.com/news"]
  28. headlines = prepare_headlines(sources, self.stopwords, timeout=10)
  29. mock_get.assert_called_once_with("https://example.com/news", allow_redirects=True, timeout=10, headers=DEFAULT_HEADERS)
  30. self.assertEqual(len(headlines), 1)
  31. @patch("services.headlines.requests.get")
  32. def test_timeout_skips_slow_request_and_processes_others(self, mock_get):
  33. slow_url = "https://slow-source.example.com"
  34. fast_url = "https://fast-source.example.com"
  35. def side_effect(url, **kwargs):
  36. if url == slow_url:
  37. raise requests.exceptions.Timeout("Connection timed out after %s seconds" % kwargs.get("timeout"))
  38. fast_response = MagicMock()
  39. fast_response.status_code = 200
  40. fast_response.content = b"<html><body><span>Breaking News Story</span></body></html>"
  41. fast_response.text = "<html><body><span>Breaking News Story</span></body></html>"
  42. return fast_response
  43. mock_get.side_effect = side_effect
  44. sources = [slow_url, fast_url]
  45. headlines = prepare_headlines(sources, self.stopwords, timeout=3)
  46. self.assertEqual(mock_get.call_count, 2)
  47. self.assertEqual(len(headlines), 1)
  48. self.assertEqual(headlines[0].display_text, "Breaking News Story")
  49. @patch("services.headlines.requests.get")
  50. def test_connect_timeout_and_read_timeout_skipped(self, mock_get):
  51. connect_timeout_url = "https://connect-timeout.com"
  52. read_timeout_url = "https://read-timeout.com"
  53. mock_get.side_effect = [
  54. requests.exceptions.ConnectTimeout("Connect timeout"),
  55. requests.exceptions.ReadTimeout("Read timeout")
  56. ]
  57. sources = [connect_timeout_url, read_timeout_url]
  58. headlines = prepare_headlines(sources, self.stopwords)
  59. self.assertEqual(mock_get.call_count, 2)
  60. self.assertEqual(len(headlines), 0)
  61. class TestHeadlineSafeguards(unittest.TestCase):
  62. def setUp(self):
  63. self.stopwords = {"the", "a", "an", "in", "on", "and", "of", "to"}
  64. def test_non_headline_link_texts_rejected(self):
  65. non_headlines = [
  66. "Account Settings",
  67. "Follow",
  68. "Television",
  69. "Sign In",
  70. "Home",
  71. "Politics",
  72. "About Us",
  73. "Contact Us",
  74. "Menu",
  75. "Search",
  76. "",
  77. " ",
  78. "123",
  79. "...",
  80. "in on a", # only stopwords
  81. ]
  82. for item in non_headlines:
  83. with self.subTest(item=item):
  84. self.assertFalse(is_headline(item, self.stopwords), f"{item!r} should not be recognized as a headline")
  85. def test_valid_headlines_accepted(self):
  86. valid_headlines = [
  87. "Breaking News Story",
  88. "Custom Timeout Headline",
  89. "Senate passes major infrastructure bill",
  90. "Scientists discover new ocean species",
  91. "Federal Reserve holds interest rates steady",
  92. ]
  93. for item in valid_headlines:
  94. with self.subTest(item=item):
  95. self.assertTrue(is_headline(item, self.stopwords), f"{item!r} should be recognized as a headline")
  96. @patch("services.headlines.requests.get")
  97. def test_prepare_headlines_filters_out_navigation_and_non_headlines(self, mock_get):
  98. html_content = """
  99. <html>
  100. <body>
  101. <a href="/settings">Account Settings</a>
  102. <a href="/social">Follow</a>
  103. <a href="/tv">Television</a>
  104. <a href="/login">Sign In</a>
  105. <a href="/article1">Senate passes major infrastructure bill</a>
  106. <span>Home</span>
  107. <span>Federal Reserve holds interest rates steady</span>
  108. </body>
  109. </html>
  110. """
  111. mock_response = MagicMock()
  112. mock_response.status_code = 200
  113. mock_response.content = html_content.encode("utf-8")
  114. mock_response.text = html_content
  115. mock_get.return_value = mock_response
  116. sources = ["https://example.com/news"]
  117. headlines = prepare_headlines(sources, self.stopwords)
  118. self.assertEqual(len(headlines), 2)
  119. extracted_texts = [h.display_text for h in headlines]
  120. self.assertIn("Senate passes major infrastructure bill", extracted_texts)
  121. self.assertIn("Federal Reserve holds interest rates steady", extracted_texts)
  122. self.assertNotIn("Account Settings", extracted_texts)
  123. self.assertNotIn("Follow", extracted_texts)
  124. self.assertNotIn("Television", extracted_texts)
  125. self.assertNotIn("Sign In", extracted_texts)
  126. self.assertNotIn("Home", extracted_texts)
  127. if __name__ == "__main__":
  128. unittest.main()