test: add 159 tests for all new modules

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)
This commit is contained in:
Yaojia Wang
2026-03-19 22:12:27 +01:00
parent ea72497587
commit 27b131492f
8 changed files with 2098 additions and 5 deletions

View File

@@ -0,0 +1,328 @@
"""Tests for fixed income 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
# --- Treasury Rates ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_rates", new_callable=AsyncMock)
async def test_treasury_rates_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-18", "week_4": 5.27, "month_3": 5.30, "year_2": 4.85, "year_10": 4.32, "year_30": 4.55}
]
resp = await client.get("/api/v1/fixed-income/treasury-rates")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 1
assert data["data"][0]["year_10"] == 4.32
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_rates", new_callable=AsyncMock)
async def test_treasury_rates_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/treasury-rates")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_rates", new_callable=AsyncMock)
async def test_treasury_rates_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("Federal Reserve API down")
resp = await client.get("/api/v1/fixed-income/treasury-rates")
assert resp.status_code == 502
# --- Yield Curve ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_yield_curve", new_callable=AsyncMock)
async def test_yield_curve_happy_path(mock_fn, client):
mock_fn.return_value = [
{"maturity": "3M", "rate": 5.30},
{"maturity": "2Y", "rate": 4.85},
{"maturity": "10Y", "rate": 4.32},
]
resp = await client.get("/api/v1/fixed-income/yield-curve")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 3
mock_fn.assert_called_once_with(date=None)
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_yield_curve", new_callable=AsyncMock)
async def test_yield_curve_with_date(mock_fn, client):
mock_fn.return_value = [{"maturity": "10Y", "rate": 3.80}]
resp = await client.get("/api/v1/fixed-income/yield-curve?date=2024-01-15")
assert resp.status_code == 200
mock_fn.assert_called_once_with(date="2024-01-15")
@pytest.mark.asyncio
async def test_yield_curve_invalid_date_format(client):
resp = await client.get("/api/v1/fixed-income/yield-curve?date=not-a-date")
assert resp.status_code == 422
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_yield_curve", new_callable=AsyncMock)
async def test_yield_curve_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/yield-curve")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- Treasury Auctions ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_auctions", new_callable=AsyncMock)
async def test_treasury_auctions_happy_path(mock_fn, client):
mock_fn.return_value = [
{"auction_date": "2026-03-10", "security_type": "Note", "security_term": "10-Year", "high_yield": 4.32, "bid_to_cover_ratio": 2.45}
]
resp = await client.get("/api/v1/fixed-income/treasury-auctions")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["bid_to_cover_ratio"] == 2.45
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_auctions", new_callable=AsyncMock)
async def test_treasury_auctions_with_security_type(mock_fn, client):
mock_fn.return_value = [{"security_type": "Bill"}]
resp = await client.get("/api/v1/fixed-income/treasury-auctions?security_type=Bill")
assert resp.status_code == 200
mock_fn.assert_called_once_with(security_type="Bill")
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_treasury_auctions", new_callable=AsyncMock)
async def test_treasury_auctions_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/treasury-auctions")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
async def test_treasury_auctions_invalid_security_type(client):
resp = await client.get("/api/v1/fixed-income/treasury-auctions?security_type=DROP;TABLE")
assert resp.status_code == 422
# --- TIPS Yields ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_tips_yields", new_callable=AsyncMock)
async def test_tips_yields_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-18", "year_5": 2.10, "year_10": 2.25, "year_30": 2.40}
]
resp = await client.get("/api/v1/fixed-income/tips-yields")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["year_10"] == 2.25
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_tips_yields", new_callable=AsyncMock)
async def test_tips_yields_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/tips-yields")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_tips_yields", new_callable=AsyncMock)
async def test_tips_yields_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("FRED unavailable")
resp = await client.get("/api/v1/fixed-income/tips-yields")
assert resp.status_code == 502
# --- EFFR ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_effr", new_callable=AsyncMock)
async def test_effr_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-18", "rate": 5.33, "percentile_1": 5.31, "percentile_25": 5.32, "percentile_75": 5.33}
]
resp = await client.get("/api/v1/fixed-income/effr")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["rate"] == 5.33
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_effr", new_callable=AsyncMock)
async def test_effr_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/effr")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- SOFR ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_sofr", new_callable=AsyncMock)
async def test_sofr_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-18", "rate": 5.31, "average_30d": 5.31, "average_90d": 5.30}
]
resp = await client.get("/api/v1/fixed-income/sofr")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["rate"] == 5.31
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_sofr", new_callable=AsyncMock)
async def test_sofr_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/sofr")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- HQM ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_hqm", new_callable=AsyncMock)
async def test_hqm_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-02-01", "aaa": 5.10, "aa": 5.25, "a": 5.40}
]
resp = await client.get("/api/v1/fixed-income/hqm")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["aaa"] == 5.10
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_hqm", new_callable=AsyncMock)
async def test_hqm_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/hqm")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- Commercial Paper ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_commercial_paper", new_callable=AsyncMock)
async def test_commercial_paper_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-18", "maturity": "overnight", "financial": 5.28, "nonfinancial": 5.30}
]
resp = await client.get("/api/v1/fixed-income/commercial-paper")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 1
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_commercial_paper", new_callable=AsyncMock)
async def test_commercial_paper_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/commercial-paper")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- Spot Rates ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_spot_rates", new_callable=AsyncMock)
async def test_spot_rates_happy_path(mock_fn, client):
mock_fn.return_value = [
{"date": "2026-03-01", "year_1": 5.50, "year_5": 5.20, "year_10": 5.10}
]
resp = await client.get("/api/v1/fixed-income/spot-rates")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["year_10"] == 5.10
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_spot_rates", new_callable=AsyncMock)
async def test_spot_rates_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/spot-rates")
assert resp.status_code == 200
assert resp.json()["data"] == []
# --- Spreads ---
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_spreads", new_callable=AsyncMock)
async def test_spreads_default(mock_fn, client):
mock_fn.return_value = [{"date": "2026-03-18", "spread": 1.10}]
resp = await client.get("/api/v1/fixed-income/spreads")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
mock_fn.assert_called_once_with(series="tcm")
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_spreads", new_callable=AsyncMock)
async def test_spreads_tcm_effr(mock_fn, client):
mock_fn.return_value = [{"date": "2026-03-18", "spread": 0.02}]
resp = await client.get("/api/v1/fixed-income/spreads?series=tcm_effr")
assert resp.status_code == 200
mock_fn.assert_called_once_with(series="tcm_effr")
@pytest.mark.asyncio
async def test_spreads_invalid_series(client):
resp = await client.get("/api/v1/fixed-income/spreads?series=invalid")
assert resp.status_code == 422
@pytest.mark.asyncio
@patch("routes_fixed_income.fixed_income_service.get_spreads", new_callable=AsyncMock)
async def test_spreads_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/fixed-income/spreads?series=treasury_effr")
assert resp.status_code == 200
assert resp.json()["data"] == []