Files
kesslerio_finance-news/tests/test_fetch_news.py

137 lines
4.1 KiB
Python
Raw Normal View History

"""Tests for RSS feed fetching and parsing."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
import json
import pytest
from unittest.mock import Mock, patch, MagicMock
from fetch_news import fetch_market_data, fetch_rss, _get_best_feed_url
from utils import clamp_timeout, compute_deadline
@pytest.fixture
def sample_rss_content():
"""Load sample RSS fixture."""
fixture_path = Path(__file__).parent / "fixtures" / "sample_rss.xml"
return fixture_path.read_bytes()
def test_fetch_rss_success(sample_rss_content):
"""Test successful RSS fetch and parse."""
with patch("urllib.request.urlopen") as mock_urlopen:
mock_response = MagicMock()
mock_response.read.return_value = sample_rss_content
mock_response.__enter__.return_value = mock_response
mock_urlopen.return_value = mock_response
articles = fetch_rss("https://example.com/feed.xml", timeout=7)
assert len(articles) == 2
assert articles[0]["title"] == "Apple Stock Rises 5%"
assert articles[1]["title"] == "Tesla Announces New Model"
assert "apple-rises" in articles[0]["link"]
assert mock_urlopen.call_args.kwargs["timeout"] == 7
def test_fetch_rss_network_error():
"""Test RSS fetch handles network errors."""
with patch("urllib.request.urlopen", side_effect=Exception("Network error")):
articles = fetch_rss("https://example.com/feed.xml")
assert articles == []
def test_get_best_feed_url_priority():
"""Test feed URL selection prioritizes 'top' key."""
source = {
"name": "Test Source",
"homepage": "https://example.com",
"top": "https://example.com/top.xml",
"markets": "https://example.com/markets.xml"
}
url = _get_best_feed_url(source)
assert url == "https://example.com/top.xml"
def test_get_best_feed_url_fallback():
"""Test feed URL falls back to other http URLs when priority keys missing."""
source = {
"name": "Test Source",
"feed": "https://example.com/feed.xml"
}
url = _get_best_feed_url(source)
assert url == "https://example.com/feed.xml"
def test_get_best_feed_url_none_if_no_urls():
"""Test returns None when no valid URLs found."""
source = {
"name": "Test Source",
"enabled": True,
"note": "No URLs here"
}
url = _get_best_feed_url(source)
assert url is None
def test_get_best_feed_url_skips_non_urls():
"""Test skips non-URL values."""
source = {
"name": "Test Source",
"enabled": True,
"count": 5,
"rss": "https://example.com/rss.xml"
}
url = _get_best_feed_url(source)
assert url == "https://example.com/rss.xml"
def test_clamp_timeout_respects_deadline(monkeypatch):
start = 100.0
monkeypatch.setattr("utils.time.monotonic", lambda: start)
deadline = compute_deadline(5)
monkeypatch.setattr("utils.time.monotonic", lambda: 103.0)
assert clamp_timeout(30, deadline) == 2
def test_clamp_timeout_deadline_exceeded(monkeypatch):
start = 200.0
monkeypatch.setattr("utils.time.monotonic", lambda: start)
deadline = compute_deadline(1)
monkeypatch.setattr("utils.time.monotonic", lambda: 205.0)
with pytest.raises(TimeoutError):
clamp_timeout(30, deadline)
def test_fetch_market_data_price_fallback(monkeypatch):
sample = {
"price": None,
"open": 100,
"prev_close": 105,
"change_percent": None,
}
def fake_run(*_args, **_kwargs):
class Result:
returncode = 0
stdout = json.dumps(sample)
stderr = ""
return Result()
monkeypatch.setattr("fetch_news.OPENBB_BINARY", "/bin/openbb-quote")
monkeypatch.setattr("fetch_news.subprocess.run", fake_run)
no_fallback = fetch_market_data(["^GSPC"], allow_price_fallback=False)
assert no_fallback["^GSPC"]["price"] is None
with_fallback = fetch_market_data(["^GSPC"], allow_price_fallback=True)
assert with_fallback["^GSPC"]["price"] == 100