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:
328
tests/test_routes_fixed_income.py
Normal file
328
tests/test_routes_fixed_income.py
Normal 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"] == []
|
||||
Reference in New Issue
Block a user