198 lines
6.8 KiB
Python
198 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Unit tests for the news domain helpers."""
|
|
|
|
from backend.domains import news as news_domain
|
|
|
|
|
|
class _FakeStore:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def get_ticker_watermarks(self, symbol):
|
|
self.calls.append(("get_ticker_watermarks", symbol))
|
|
return {"symbol": symbol, "last_news_fetch": "2026-03-10"}
|
|
|
|
def get_news_items_enriched(self, ticker, start_date=None, end_date=None, trade_date=None, limit=100):
|
|
self.calls.append(("get_news_items_enriched", ticker, start_date, end_date, trade_date, limit))
|
|
target = trade_date or end_date
|
|
return [{"id": "n1", "ticker": ticker, "date": target, "trade_date": target}]
|
|
|
|
def get_news_timeline_enriched(self, ticker, start_date=None, end_date=None):
|
|
self.calls.append(("get_news_timeline_enriched", ticker, start_date, end_date))
|
|
return [{"date": end_date, "count": 1}]
|
|
|
|
def get_news_categories_enriched(self, ticker, start_date=None, end_date=None, limit=200):
|
|
self.calls.append(("get_news_categories_enriched", ticker, start_date, end_date, limit))
|
|
return {"macro": {"count": 1}}
|
|
|
|
def get_news_by_ids_enriched(self, ticker, article_ids):
|
|
self.calls.append(("get_news_by_ids_enriched", ticker, list(article_ids)))
|
|
return [{"id": article_ids[0], "ticker": ticker, "date": "2026-03-16"}]
|
|
|
|
|
|
def test_news_rows_need_enrichment_detects_missing_fields():
|
|
assert news_domain.news_rows_need_enrichment([]) is True
|
|
assert news_domain.news_rows_need_enrichment([{"sentiment": "", "relevance": "", "key_discussion": ""}]) is True
|
|
assert news_domain.news_rows_need_enrichment([{"sentiment": "positive"}]) is False
|
|
|
|
|
|
def test_ensure_news_fresh_triggers_incremental_refresh_when_watermark_is_stale(monkeypatch):
|
|
store = _FakeStore()
|
|
calls = []
|
|
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"update_ticker_incremental",
|
|
lambda symbol, end_date=None, store=None: calls.append((symbol, end_date)),
|
|
)
|
|
|
|
payload = news_domain.ensure_news_fresh(store, ticker="AAPL", target_date="2026-03-16")
|
|
|
|
assert calls == [("AAPL", "2026-03-16")]
|
|
assert payload["target_date"] == "2026-03-16"
|
|
assert payload["refreshed"] is True
|
|
|
|
|
|
def test_ensure_news_fresh_skips_refresh_when_watermark_is_current(monkeypatch):
|
|
store = _FakeStore()
|
|
calls = []
|
|
|
|
monkeypatch.setattr(
|
|
store,
|
|
"get_ticker_watermarks",
|
|
lambda symbol: {"symbol": symbol, "last_news_fetch": "2026-03-16"},
|
|
)
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"update_ticker_incremental",
|
|
lambda symbol, end_date=None, store=None: calls.append((symbol, end_date)),
|
|
)
|
|
|
|
payload = news_domain.ensure_news_fresh(store, ticker="AAPL", target_date="2026-03-16")
|
|
|
|
assert calls == []
|
|
assert payload["refreshed"] is False
|
|
|
|
|
|
def test_get_enriched_news_returns_rows_without_enrichment_when_present(monkeypatch):
|
|
store = _FakeStore()
|
|
monkeypatch.setattr(news_domain, "news_rows_need_enrichment", lambda rows: False)
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"ensure_news_fresh",
|
|
lambda store, ticker, target_date=None, refresh_if_stale=False: {
|
|
"ticker": ticker,
|
|
"target_date": target_date,
|
|
"last_news_fetch": target_date,
|
|
"refreshed": False,
|
|
},
|
|
)
|
|
|
|
payload = news_domain.get_enriched_news(
|
|
store,
|
|
ticker="AAPL",
|
|
start_date="2026-03-01",
|
|
end_date="2026-03-16",
|
|
limit=20,
|
|
)
|
|
|
|
assert payload["ticker"] == "AAPL"
|
|
assert payload["news"][0]["ticker"] == "AAPL"
|
|
assert payload["freshness"]["target_date"] is None or payload["freshness"]["target_date"] == "2026-03-16"
|
|
assert store.calls == [
|
|
("get_news_items_enriched", "AAPL", "2026-03-01", "2026-03-16", None, 20)
|
|
]
|
|
|
|
|
|
def test_get_story_and_similar_days_delegate(monkeypatch):
|
|
store = _FakeStore()
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"ensure_news_fresh",
|
|
lambda store, ticker, target_date=None, refresh_if_stale=False: {
|
|
"ticker": ticker,
|
|
"target_date": target_date,
|
|
"last_news_fetch": target_date,
|
|
"refreshed": False,
|
|
},
|
|
)
|
|
monkeypatch.setattr(news_domain, "enrich_news_for_symbol", lambda *args, **kwargs: {"analyzed": 1})
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"get_or_create_stock_story",
|
|
lambda store, symbol, as_of_date: {"symbol": symbol, "as_of_date": as_of_date, "story": "story"},
|
|
)
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"find_similar_days",
|
|
lambda store, symbol, target_date, top_k: {"symbol": symbol, "target_date": target_date, "items": [{"score": 0.9}]},
|
|
)
|
|
|
|
story = news_domain.get_story_payload(store, ticker="AAPL", as_of_date="2026-03-16")
|
|
similar = news_domain.get_similar_days_payload(store, ticker="AAPL", date="2026-03-16", n_similar=8)
|
|
|
|
assert story["story"] == "story"
|
|
assert "freshness" in story
|
|
assert similar["items"][0]["score"] == 0.9
|
|
assert "freshness" in similar
|
|
|
|
|
|
def test_get_enriched_news_defaults_to_read_only_freshness(monkeypatch):
|
|
store = _FakeStore()
|
|
ensure_calls = []
|
|
|
|
def fake_ensure(store, ticker, target_date=None, refresh_if_stale=False):
|
|
ensure_calls.append(refresh_if_stale)
|
|
return {
|
|
"ticker": ticker,
|
|
"target_date": target_date,
|
|
"last_news_fetch": target_date,
|
|
"refreshed": False,
|
|
}
|
|
|
|
monkeypatch.setattr(news_domain, "ensure_news_fresh", fake_ensure)
|
|
monkeypatch.setattr(news_domain, "news_rows_need_enrichment", lambda rows: False)
|
|
|
|
payload = news_domain.get_enriched_news(
|
|
store,
|
|
ticker="AAPL",
|
|
end_date="2026-03-16",
|
|
)
|
|
|
|
assert payload["ticker"] == "AAPL"
|
|
assert ensure_calls == [False]
|
|
|
|
|
|
def test_get_range_explain_payload_uses_article_ids(monkeypatch):
|
|
store = _FakeStore()
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"ensure_news_fresh",
|
|
lambda store, ticker, target_date=None, refresh_if_stale=False: {
|
|
"ticker": ticker,
|
|
"target_date": target_date,
|
|
"last_news_fetch": target_date,
|
|
"refreshed": False,
|
|
},
|
|
)
|
|
monkeypatch.setattr(news_domain, "news_rows_need_enrichment", lambda rows: False)
|
|
monkeypatch.setattr(
|
|
news_domain,
|
|
"build_range_explanation",
|
|
lambda ticker, start_date, end_date, news_rows: {"ticker": ticker, "count": len(news_rows)},
|
|
)
|
|
|
|
payload = news_domain.get_range_explain_payload(
|
|
store,
|
|
ticker="AAPL",
|
|
start_date="2026-03-10",
|
|
end_date="2026-03-16",
|
|
article_ids=["news-9"],
|
|
limit=50,
|
|
)
|
|
|
|
assert payload["ticker"] == "AAPL"
|
|
assert payload["result"] == {"ticker": "AAPL", "count": 1}
|
|
assert "freshness" in payload
|
|
assert store.calls == [("get_news_by_ids_enriched", "AAPL", ["news-9"])]
|