New test files (171 tests): - test_routes_shorts.py (16) - short volume, FTD, interest, darkpool - test_routes_fixed_income.py (34) - treasury, yield curve, SOFR, etc. - test_routes_economy.py (44) - CPI, GDP, FRED search, Fed holdings - test_routes_surveys.py (17) - Michigan, SLOOS, NFP, Empire State - test_routes_regulators.py (20) - COT, SEC litigation, institutions - test_finnhub_service_social.py (20) - social/reddit sentiment unit tests - test_routes_sentiment_social.py (20) - social endpoints + composite Updated: - test_routes_sentiment.py - match new composite sentiment response shape Total: 261 tests passing (was 102)
434 lines
15 KiB
Python
434 lines
15 KiB
Python
"""Tests for expanded economy routes."""
|
|
|
|
from unittest.mock import patch, AsyncMock
|
|
|
|
import pytest
|
|
from httpx import AsyncClient, ASGITransport
|
|
|
|
from main import app
|
|
|
|
|
|
@pytest.fixture
|
|
async def client():
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as c:
|
|
yield c
|
|
|
|
|
|
# --- CPI ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_cpi", new_callable=AsyncMock)
|
|
async def test_macro_cpi_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [
|
|
{"date": "2026-02-01", "value": 312.5, "country": "united_states"}
|
|
]
|
|
resp = await client.get("/api/v1/macro/cpi")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["value"] == 312.5
|
|
mock_fn.assert_called_once_with(country="united_states")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_cpi", new_callable=AsyncMock)
|
|
async def test_macro_cpi_custom_country(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-02-01", "value": 120.0}]
|
|
resp = await client.get("/api/v1/macro/cpi?country=germany")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(country="germany")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_macro_cpi_invalid_country(client):
|
|
resp = await client.get("/api/v1/macro/cpi?country=INVALID!!!COUNTRY")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_cpi", new_callable=AsyncMock)
|
|
async def test_macro_cpi_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/cpi")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_cpi", new_callable=AsyncMock)
|
|
async def test_macro_cpi_service_error_returns_502(mock_fn, client):
|
|
mock_fn.side_effect = RuntimeError("FRED down")
|
|
resp = await client.get("/api/v1/macro/cpi")
|
|
assert resp.status_code == 502
|
|
|
|
|
|
# --- GDP ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_gdp", new_callable=AsyncMock)
|
|
async def test_macro_gdp_default_real(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-01-01", "value": 22.5}]
|
|
resp = await client.get("/api/v1/macro/gdp")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
mock_fn.assert_called_once_with(gdp_type="real")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_gdp", new_callable=AsyncMock)
|
|
async def test_macro_gdp_nominal(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-01-01", "value": 28.3}]
|
|
resp = await client.get("/api/v1/macro/gdp?gdp_type=nominal")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(gdp_type="nominal")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_macro_gdp_invalid_type(client):
|
|
resp = await client.get("/api/v1/macro/gdp?gdp_type=invalid")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_gdp", new_callable=AsyncMock)
|
|
async def test_macro_gdp_forecast(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2027-01-01", "value": 23.1}]
|
|
resp = await client.get("/api/v1/macro/gdp?gdp_type=forecast")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(gdp_type="forecast")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_gdp", new_callable=AsyncMock)
|
|
async def test_macro_gdp_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/gdp")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- Unemployment ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_unemployment", new_callable=AsyncMock)
|
|
async def test_macro_unemployment_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-02-01", "value": 3.7, "country": "united_states"}]
|
|
resp = await client.get("/api/v1/macro/unemployment")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["value"] == 3.7
|
|
mock_fn.assert_called_once_with(country="united_states")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_unemployment", new_callable=AsyncMock)
|
|
async def test_macro_unemployment_custom_country(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-02-01", "value": 5.1}]
|
|
resp = await client.get("/api/v1/macro/unemployment?country=france")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(country="france")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_unemployment", new_callable=AsyncMock)
|
|
async def test_macro_unemployment_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/unemployment")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- PCE ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_pce", new_callable=AsyncMock)
|
|
async def test_macro_pce_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-02-01", "value": 2.8}]
|
|
resp = await client.get("/api/v1/macro/pce")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["value"] == 2.8
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_pce", new_callable=AsyncMock)
|
|
async def test_macro_pce_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/pce")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_pce", new_callable=AsyncMock)
|
|
async def test_macro_pce_service_error_returns_502(mock_fn, client):
|
|
mock_fn.side_effect = RuntimeError("FRED unavailable")
|
|
resp = await client.get("/api/v1/macro/pce")
|
|
assert resp.status_code == 502
|
|
|
|
|
|
# --- Money Measures ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_money_measures", new_callable=AsyncMock)
|
|
async def test_macro_money_measures_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-02-01", "m1": 18200.0, "m2": 21000.0}]
|
|
resp = await client.get("/api/v1/macro/money-measures")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["m2"] == 21000.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_money_measures", new_callable=AsyncMock)
|
|
async def test_macro_money_measures_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/money-measures")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- CLI ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_composite_leading_indicator", new_callable=AsyncMock)
|
|
async def test_macro_cli_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-01-01", "value": 99.2, "country": "united_states"}]
|
|
resp = await client.get("/api/v1/macro/cli")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["value"] == 99.2
|
|
mock_fn.assert_called_once_with(country="united_states")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_composite_leading_indicator", new_callable=AsyncMock)
|
|
async def test_macro_cli_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/cli")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- House Price Index ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_house_price_index", new_callable=AsyncMock)
|
|
async def test_macro_hpi_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-01-01", "value": 350.0, "country": "united_states"}]
|
|
resp = await client.get("/api/v1/macro/house-price-index")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["value"] == 350.0
|
|
mock_fn.assert_called_once_with(country="united_states")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_house_price_index", new_callable=AsyncMock)
|
|
async def test_macro_hpi_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/macro/house-price-index")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- FRED Regional ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fred_regional", new_callable=AsyncMock)
|
|
async def test_economy_fred_regional_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"region": "CA", "value": 5.2}]
|
|
resp = await client.get("/api/v1/economy/fred-regional?series_id=CAUR")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["region"] == "CA"
|
|
mock_fn.assert_called_once_with(series_id="CAUR", region=None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fred_regional", new_callable=AsyncMock)
|
|
async def test_economy_fred_regional_with_region(mock_fn, client):
|
|
mock_fn.return_value = [{"region": "state", "value": 4.1}]
|
|
resp = await client.get("/api/v1/economy/fred-regional?series_id=CAUR®ion=state")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(series_id="CAUR", region="state")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_economy_fred_regional_missing_series_id(client):
|
|
resp = await client.get("/api/v1/economy/fred-regional")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fred_regional", new_callable=AsyncMock)
|
|
async def test_economy_fred_regional_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/fred-regional?series_id=UNKNOWN")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- Primary Dealer Positioning ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_primary_dealer_positioning", new_callable=AsyncMock)
|
|
async def test_economy_primary_dealer_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-03-12", "treasuries": 250000.0, "mbs": 80000.0}]
|
|
resp = await client.get("/api/v1/economy/primary-dealer-positioning")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["treasuries"] == 250000.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_primary_dealer_positioning", new_callable=AsyncMock)
|
|
async def test_economy_primary_dealer_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/primary-dealer-positioning")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- FRED Search ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.fred_search", new_callable=AsyncMock)
|
|
async def test_economy_fred_search_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [
|
|
{"id": "FEDFUNDS", "title": "Effective Federal Funds Rate", "frequency": "Monthly"}
|
|
]
|
|
resp = await client.get("/api/v1/economy/fred-search?query=federal+funds")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["id"] == "FEDFUNDS"
|
|
mock_fn.assert_called_once_with(query="federal funds")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_economy_fred_search_missing_query(client):
|
|
resp = await client.get("/api/v1/economy/fred-search")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.fred_search", new_callable=AsyncMock)
|
|
async def test_economy_fred_search_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/fred-search?query=nothingtofind")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- Balance of Payments ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_balance_of_payments", new_callable=AsyncMock)
|
|
async def test_economy_bop_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-01-01", "current_account": -200.0, "capital_account": 5.0}]
|
|
resp = await client.get("/api/v1/economy/balance-of-payments")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["current_account"] == -200.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_balance_of_payments", new_callable=AsyncMock)
|
|
async def test_economy_bop_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/balance-of-payments")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- Central Bank Holdings ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_central_bank_holdings", new_callable=AsyncMock)
|
|
async def test_economy_central_bank_holdings_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2026-03-13", "treasuries": 4500000.0, "mbs": 2300000.0}]
|
|
resp = await client.get("/api/v1/economy/central-bank-holdings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["treasuries"] == 4500000.0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_central_bank_holdings", new_callable=AsyncMock)
|
|
async def test_economy_central_bank_holdings_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/central-bank-holdings")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|
|
|
|
|
|
# --- FOMC Documents ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fomc_documents", new_callable=AsyncMock)
|
|
async def test_economy_fomc_happy_path(mock_fn, client):
|
|
mock_fn.return_value = [
|
|
{"date": "2026-01-28", "type": "Minutes", "url": "https://federalreserve.gov/fomc"}
|
|
]
|
|
resp = await client.get("/api/v1/economy/fomc-documents")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["success"] is True
|
|
assert data["data"][0]["type"] == "Minutes"
|
|
mock_fn.assert_called_once_with(year=None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fomc_documents", new_callable=AsyncMock)
|
|
async def test_economy_fomc_with_year(mock_fn, client):
|
|
mock_fn.return_value = [{"date": "2024-01-30", "type": "Statement"}]
|
|
resp = await client.get("/api/v1/economy/fomc-documents?year=2024")
|
|
assert resp.status_code == 200
|
|
mock_fn.assert_called_once_with(year=2024)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_economy_fomc_invalid_year_too_low(client):
|
|
resp = await client.get("/api/v1/economy/fomc-documents?year=1999")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_economy_fomc_invalid_year_too_high(client):
|
|
resp = await client.get("/api/v1/economy/fomc-documents?year=2100")
|
|
assert resp.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("routes_economy.economy_service.get_fomc_documents", new_callable=AsyncMock)
|
|
async def test_economy_fomc_empty(mock_fn, client):
|
|
mock_fn.return_value = []
|
|
resp = await client.get("/api/v1/economy/fomc-documents")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["data"] == []
|