# -*- coding: utf-8 -*- """Tests for the extracted news service app surface.""" from fastapi.testclient import TestClient from backend.apps.news_service import create_app class _FakeStore: def get_ticker_watermarks(self, symbol): return {"symbol": symbol, "last_news_fetch": "2026-12-31"} def get_news_timeline_enriched(self, symbol, start_date=None, end_date=None): return [{"date": end_date, "count": 1}] def get_news_items(self, symbol, start_date=None, end_date=None, limit=100): return [{"id": "news-raw-1", "ticker": symbol, "title": "Raw Title", "date": end_date}] def get_news_items_enriched(self, symbol, start_date=None, end_date=None, trade_date=None, limit=100): return [{"id": "news-1", "ticker": symbol, "title": "Title", "date": trade_date or end_date}] def upsert_news_analysis(self, symbol, rows): return len(rows) def get_analyzed_news_ids(self, symbol, start_date=None, end_date=None): return set() def get_news_categories_enriched(self, symbol, start_date=None, end_date=None, limit=200): return {"market": {"label": "market", "count": 1, "article_ids": ["news-1"]}} def get_news_by_ids_enriched(self, symbol, article_ids): return [{"id": article_ids[0], "ticker": symbol, "title": "Picked"}] def test_news_service_routes_are_exposed(): app = create_app() paths = {route.path for route in app.routes} assert "/health" in paths assert "/api/enriched-news" in paths assert "/api/news-for-date" in paths assert "/api/news-timeline" in paths assert "/api/categories" in paths assert "/api/similar-days" in paths assert "/api/stories/{ticker}" in paths assert "/api/range-explain" in paths def test_news_service_enriched_news_and_categories(monkeypatch): app = create_app() app.dependency_overrides.clear() from backend.apps import news_service as news_service_module app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore() monkeypatch.setattr( "backend.domains.news.enrich_news_for_symbol", lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1}, ) with TestClient(app) as client: news_response = client.get( "/api/enriched-news", params={"ticker": "AAPL", "end_date": "2026-03-23"}, ) categories_response = client.get( "/api/categories", params={"ticker": "AAPL", "end_date": "2026-03-23"}, ) assert news_response.status_code == 200 assert news_response.json()["news"][0]["ticker"] == "AAPL" assert categories_response.status_code == 200 assert categories_response.json()["categories"]["market"]["count"] == 1 def test_news_service_news_for_date_and_timeline(monkeypatch): app = create_app() from backend.apps import news_service as news_service_module app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore() monkeypatch.setattr( "backend.domains.news.enrich_news_for_symbol", lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1}, ) with TestClient(app) as client: date_response = client.get( "/api/news-for-date", params={"ticker": "AAPL", "date": "2026-03-23"}, ) timeline_response = client.get( "/api/news-timeline", params={ "ticker": "AAPL", "start_date": "2026-03-01", "end_date": "2026-03-23", }, ) assert date_response.status_code == 200 assert date_response.json()["date"] == "2026-03-23" assert timeline_response.status_code == 200 assert timeline_response.json()["timeline"][0]["count"] == 1 def test_news_service_similar_days_and_story(monkeypatch): app = create_app() from backend.apps import news_service as news_service_module app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore() monkeypatch.setattr( "backend.domains.news.enrich_news_for_symbol", lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1}, ) monkeypatch.setattr( "backend.domains.news.find_similar_days", lambda store, symbol, target_date, top_k: { "symbol": symbol, "target_date": target_date, "items": [{"date": "2026-03-20", "score": 0.9}], }, ) monkeypatch.setattr( "backend.domains.news.get_or_create_stock_story", lambda store, symbol, as_of_date: { "symbol": symbol, "as_of_date": as_of_date, "story": "story body", "source": "local", }, ) with TestClient(app) as client: similar_response = client.get( "/api/similar-days", params={"ticker": "AAPL", "date": "2026-03-23", "n_similar": 3}, ) story_response = client.get( "/api/stories/AAPL", params={"as_of_date": "2026-03-23"}, ) assert similar_response.status_code == 200 assert similar_response.json()["items"][0]["score"] == 0.9 assert story_response.status_code == 200 assert story_response.json()["story"] == "story body" def test_news_service_range_explain(monkeypatch): app = create_app() from backend.apps import news_service as news_service_module app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore() monkeypatch.setattr( "backend.domains.news.enrich_news_for_symbol", lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1}, ) monkeypatch.setattr( "backend.domains.news.build_range_explanation", lambda ticker, start_date, end_date, news_rows: { "symbol": ticker, "news_count": len(news_rows), "start_date": start_date, "end_date": end_date, }, ) with TestClient(app) as client: response = client.get( "/api/range-explain", params={ "ticker": "AAPL", "start_date": "2026-03-01", "end_date": "2026-03-23", "article_ids": ["news-7"], }, ) assert response.status_code == 200 assert response.json()["result"]["news_count"] == 1