Files
All-in-one-Financial-Analysis/atlas-terminal/tests/test_smoke.py
T
shawnkim1997 ef429e93c4 feat: daily news workbench and Korean-aware ticker search
- /daily-news route fetches FT ePaper headlines with Gemini-backed Korean translation, plus calendar selector and cached translation TTL
- /api/search and use-ticker-search wire async sidebar search through a unified backend that merges static aliases with the local KOSPI/KOSDAQ universe (162 names) and yfinance metadata
- ticker-alias gains Korean display names, currency and market hints; PeerComparison formats KRW/JPY with locale-aware zero decimals
- EquityOverview surfaces HQ city/country, with Korean Naver snapshot fallback when yfinance info is empty
- market_data adds /korean-universe/search for autocomplete
- New tests for FT ingestion, Korean universe lookups, and updated smoke prefixes
2026-05-10 23:56:27 +01:00

299 lines
9.8 KiB
Python

"""Smoke tests that keep the FastAPI shell safe during refactors."""
from datetime import date, timedelta
from fastapi.testclient import TestClient
from server.core.providers.base import DataUnavailable
from server.core.data_gateway import Fundamentals, HoldersData, OHLCV, OHLCVBar, Profile, Quote
from server.main import app
EXPECTED_PREFIXES = [
"/api/analysis",
"/api/chat",
"/api/calendar",
"/api/crypto",
"/api/copilot",
"/api/credentials",
"/api/daily-news",
"/api/dart",
"/api/earnings",
"/api/edgar",
"/api/edinet",
"/api/estimates",
"/api/financials",
"/api/fmp",
"/api/fx",
"/api/insider",
"/api/macro",
"/api/market",
"/api/markets",
"/api/news",
"/api/portfolio",
"/api/research",
"/api/screener",
"/api/technical",
"/api/tax",
"/api/valuation",
]
def test_health_endpoint_returns_ok() -> None:
with TestClient(app) as client:
response = client.get("/api/health")
assert response.status_code == 200
assert response.json()["status"] == "ok"
def test_openapi_contains_all_router_prefixes() -> None:
with TestClient(app) as client:
response = client.get("/openapi.json")
assert response.status_code == 200
paths = response.json()["paths"].keys()
for prefix in EXPECTED_PREFIXES:
assert any(path.startswith(prefix) for path in paths), prefix
def test_lightweight_asset_type_endpoint(monkeypatch) -> None:
from server.routers import market_data
monkeypatch.setattr(market_data, "detect_asset_type", lambda ticker: market_data.AssetType.EQUITY)
with TestClient(app) as client:
response = client.get("/api/market/asset-type/AAPL")
assert response.status_code == 200
assert response.json() == {"ticker": "AAPL", "asset_type": "equity"}
def test_quote_endpoint_can_use_gateway_flag(monkeypatch) -> None:
from server.routers import market_data
class FakeGateway:
async def quote(self, ticker: str) -> Quote:
return Quote(symbol=ticker.upper(), price=123.45, change_pct=1.234, source="fake")
monkeypatch.setattr(market_data.core_flags, "new_data_gateway_enabled", lambda: True)
monkeypatch.setattr(market_data, "get_data_gateway", lambda: FakeGateway())
with TestClient(app) as client:
response = client.get("/api/market/quote/AAPL")
assert response.status_code == 200
assert response.json() == {"ticker": "AAPL", "current_price": 123.45, "change_pct": 1.23}
def test_quote_endpoint_gateway_failure_degrades(monkeypatch) -> None:
from server.routers import market_data
class FailingGateway:
async def quote(self, ticker: str) -> Quote:
raise DataUnavailable(ticker, "quote")
monkeypatch.setattr(market_data.core_flags, "new_data_gateway_enabled", lambda: True)
monkeypatch.setattr(market_data, "get_data_gateway", lambda: FailingGateway())
with TestClient(app) as client:
response = client.get("/api/market/quote/AAPL")
assert response.status_code == 200
assert response.json() == {"ticker": "AAPL", "current_price": None, "change_pct": None}
def test_peer_endpoint_returns_gateway_matrix(monkeypatch) -> None:
from server.routers import market_data
class FakeGateway:
async def profile(self, ticker: str) -> Profile:
return Profile(symbol=ticker.upper(), sector="Technology", industry="Semiconductors", source="fake")
async def peers(self, ticker: str) -> list[str]:
return ["AMD", "NVDA"]
async def fundamentals(self, ticker: str, period: str = "ttm") -> Fundamentals:
rows = {
"NVDA": Fundamentals(
symbol="NVDA",
period=period,
name="NVIDIA",
market_cap=3_000_000_000_000,
pe=40.0,
ev_ebitda=32.0,
roic=0.45,
gross_margin=0.72,
revenue_growth=0.6,
source="fake",
),
"AMD": Fundamentals(
symbol="AMD",
period=period,
name="AMD",
market_cap=250_000_000_000,
pe=35.0,
ev_ebitda=25.0,
roic=0.12,
gross_margin=0.5,
revenue_growth=0.1,
source="fake",
),
}
return rows[ticker.upper()]
monkeypatch.setattr(market_data, "get_data_gateway", lambda: FakeGateway())
with TestClient(app) as client:
response = client.get("/api/market/peers/NVDA?metrics=pe,ev_ebitda,roic,gross_margin")
assert response.status_code == 200
data = response.json()
assert data["primary"] == "NVDA"
assert data["peer_symbols"] == ["AMD"]
assert data["metrics"] == ["pe", "ev_ebitda", "roic", "gross_margin"]
assert [row["ticker"] for row in data["matrix"]] == ["NVDA", "AMD"]
assert data["averages"]["pe"] == 37.5
def test_transcript_delta_degrades_without_fmp_key(monkeypatch) -> None:
monkeypatch.delenv("FMP_API_KEY", raising=False)
with TestClient(app) as client:
response = client.get("/api/earnings/NVDA/transcript-delta")
assert response.status_code == 200
assert response.json()["available"] is False
def test_calendar_degrades_without_fmp_key(monkeypatch) -> None:
monkeypatch.delenv("FMP_API_KEY", raising=False)
with TestClient(app) as client:
response = client.get("/api/calendar/economic")
assert response.status_code == 200
data = response.json()
assert data["available"] is False
assert data["grouped"] == {}
def test_financial_statement_table_uses_gateway(monkeypatch) -> None:
from server.routers import financials
class FakeGateway:
async def financials(self, ticker: str, statement: str = "income", period: str = "annual") -> dict:
return {
"ticker": ticker.upper(),
"statement": statement,
"period": period,
"source": "fake",
"periods": ["2025", "2024"],
"line_items": {"revenue": [120.0, 100.0]},
}
monkeypatch.setattr(financials, "get_data_gateway", lambda: FakeGateway())
with TestClient(app) as client:
response = client.get("/api/financials/AAPL/table?statement=income&period=annual")
assert response.status_code == 200
assert response.json()["line_items"]["revenue"] == [120.0, 100.0]
def test_ownership_endpoint_normalizes_gateway_rows(monkeypatch) -> None:
from server.routers import market_data
class FakeGateway:
async def holders(self, ticker: str) -> HoldersData:
return HoldersData(
symbol=ticker.upper(),
institutions=[
{"Holder": "Vanguard", "Shares": 1000, "pctHeld": 0.12, "Value": 250000, "Change": 25},
],
insiders=[
{"Name": "CEO Example", "Shares Owned Directly": 100, "change": -5},
],
source="fake",
)
monkeypatch.setattr(market_data, "get_data_gateway", lambda: FakeGateway())
with TestClient(app) as client:
response = client.get("/api/market/ownership/NVDA")
assert response.status_code == 200
data = response.json()
assert data["available"] is True
assert data["institutional_pct"] == 12.0
assert data["institutions"][0]["name"] == "Vanguard"
assert data["insiders"][0]["name"] == "CEO Example"
def test_portfolio_correlation_uses_gateway_history(monkeypatch) -> None:
from server.routers import portfolio
monkeypatch.setattr(
portfolio,
"_load_positions",
lambda: [
{"ticker": "AAA", "quantity": 1, "avg_price": 10},
{"ticker": "BBB", "quantity": 1, "avg_price": 10},
],
)
class FakeGateway:
async def history(self, ticker: str, range_key: str = "3mo") -> OHLCV:
base = 10 if ticker == "AAA" else 20
bars = [
OHLCVBar(date=date(2026, 1, 1) + timedelta(days=idx), open=None, high=None, low=None, close=base + idx)
for idx in range(5)
]
return OHLCV(symbol=ticker, range=range_key, bars=bars, source="fake")
monkeypatch.setattr(portfolio, "get_data_gateway", lambda: FakeGateway())
with TestClient(app) as client:
response = client.get("/api/portfolio/correlation?window=90")
assert response.status_code == 200
data = response.json()
assert data["available"] is True
assert data["tickers"] == ["AAA", "BBB"]
assert data["matrix"][0][0] == 1.0
def test_uk_cgt_calculator_uses_portfolio_positions(monkeypatch) -> None:
from server.routers import tax
monkeypatch.setattr(
tax,
"_load_positions",
lambda: [
{"ticker": "AAPL", "quantity": 10, "avg_price": 100, "currency": "USD"},
],
)
monkeypatch.setattr(tax, "_get_current_quote", lambda ticker, exchange="": {"price": 200, "currency": "USD"})
with TestClient(app) as client:
response = client.get("/api/tax/uk/cgt/local?income_band=higher")
assert response.status_code == 200
data = response.json()
assert data["total_unrealized_gain_gbp"] == 800.0
assert data["tax_if_sold_all"] == 0.0
assert data["positions"][0]["ticker"] == "AAPL"
def test_red_team_degrades_without_api_key() -> None:
with TestClient(app) as client:
response = client.post(
"/api/copilot/red-team",
json={"ticker": "NVDA", "thesis": "NVDA can keep compounding because AI demand remains structurally strong."},
)
assert response.status_code == 200
data = response.json()
assert data["source"] == "rules"
assert len(data["critiques"]) >= 3