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,228 @@
"""Tests for regulatory data routes (CFTC, SEC)."""
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
# --- COT Report ---
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cot", new_callable=AsyncMock)
async def test_cot_report_happy_path(mock_fn, client):
mock_fn.return_value = [
{
"date": "2026-03-11",
"symbol": "ES",
"commercial_long": 250000,
"commercial_short": 300000,
"noncommercial_long": 180000,
"noncommercial_short": 120000,
}
]
resp = await client.get("/api/v1/regulators/cot?symbol=ES")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["commercial_long"] == 250000
mock_fn.assert_called_once_with("ES")
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cot", new_callable=AsyncMock)
async def test_cot_report_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/regulators/cot?symbol=UNKNOWN")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cot", new_callable=AsyncMock)
async def test_cot_report_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("CFTC unavailable")
resp = await client.get("/api/v1/regulators/cot?symbol=ES")
assert resp.status_code == 502
@pytest.mark.asyncio
async def test_cot_report_missing_symbol(client):
resp = await client.get("/api/v1/regulators/cot")
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_cot_report_invalid_symbol(client):
resp = await client.get("/api/v1/regulators/cot?symbol=DROP;TABLE")
assert resp.status_code == 400
# --- COT Search ---
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.cot_search", new_callable=AsyncMock)
async def test_cot_search_happy_path(mock_fn, client):
mock_fn.return_value = [
{"code": "13874P", "name": "E-MINI S&P 500"},
{"code": "13874A", "name": "S&P 500 CONSOLIDATED"},
]
resp = await client.get("/api/v1/regulators/cot/search?query=S%26P+500")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 2
assert data["data"][0]["name"] == "E-MINI S&P 500"
@pytest.mark.asyncio
async def test_cot_search_missing_query(client):
resp = await client.get("/api/v1/regulators/cot/search")
assert resp.status_code == 422
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.cot_search", new_callable=AsyncMock)
async def test_cot_search_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/regulators/cot/search?query=nonexistentfutures")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.cot_search", new_callable=AsyncMock)
async def test_cot_search_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("CFTC search failed")
resp = await client.get("/api/v1/regulators/cot/search?query=gold")
assert resp.status_code == 502
# --- SEC Litigation ---
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_sec_litigation", new_callable=AsyncMock)
async def test_sec_litigation_happy_path(mock_fn, client):
mock_fn.return_value = [
{
"date": "2026-03-15",
"title": "SEC Charges Former CEO with Fraud",
"url": "https://sec.gov/litigation/lr/2026/lr-99999.htm",
"summary": "The Commission charged...",
}
]
resp = await client.get("/api/v1/regulators/sec/litigation")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 1
assert "CEO" in data["data"][0]["title"]
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_sec_litigation", new_callable=AsyncMock)
async def test_sec_litigation_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/regulators/sec/litigation")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_sec_litigation", new_callable=AsyncMock)
async def test_sec_litigation_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("SEC RSS feed unavailable")
resp = await client.get("/api/v1/regulators/sec/litigation")
assert resp.status_code == 502
# --- SEC Institution Search ---
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.search_institutions", new_callable=AsyncMock)
async def test_sec_institutions_happy_path(mock_fn, client):
mock_fn.return_value = [
{"name": "Vanguard Group Inc", "cik": "0000102909"},
{"name": "BlackRock Inc", "cik": "0001364742"},
]
resp = await client.get("/api/v1/regulators/sec/institutions?query=vanguard")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert len(data["data"]) == 2
assert data["data"][0]["name"] == "Vanguard Group Inc"
mock_fn.assert_called_once_with("vanguard")
@pytest.mark.asyncio
async def test_sec_institutions_missing_query(client):
resp = await client.get("/api/v1/regulators/sec/institutions")
assert resp.status_code == 422
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.search_institutions", new_callable=AsyncMock)
async def test_sec_institutions_empty(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/regulators/sec/institutions?query=notarealfirm")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.search_institutions", new_callable=AsyncMock)
async def test_sec_institutions_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("SEC API failed")
resp = await client.get("/api/v1/regulators/sec/institutions?query=blackrock")
assert resp.status_code == 502
# --- SEC CIK Map ---
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cik_map", new_callable=AsyncMock)
async def test_sec_cik_map_happy_path(mock_fn, client):
mock_fn.return_value = [{"symbol": "AAPL", "cik": "0000320193"}]
resp = await client.get("/api/v1/regulators/sec/cik-map/AAPL")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["data"][0]["cik"] == "0000320193"
mock_fn.assert_called_once_with("AAPL")
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cik_map", new_callable=AsyncMock)
async def test_sec_cik_map_not_found(mock_fn, client):
mock_fn.return_value = []
resp = await client.get("/api/v1/regulators/sec/cik-map/XXXX")
assert resp.status_code == 200
assert resp.json()["data"] == []
@pytest.mark.asyncio
@patch("routes_regulators.regulators_service.get_cik_map", new_callable=AsyncMock)
async def test_sec_cik_map_service_error_returns_502(mock_fn, client):
mock_fn.side_effect = RuntimeError("SEC lookup failed")
resp = await client.get("/api/v1/regulators/sec/cik-map/AAPL")
assert resp.status_code == 502
@pytest.mark.asyncio
async def test_sec_cik_map_invalid_symbol(client):
resp = await client.get("/api/v1/regulators/sec/cik-map/INVALID!!!")
assert resp.status_code == 400