137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
"""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
|