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:
228
tests/test_routes_regulators.py
Normal file
228
tests/test_routes_regulators.py
Normal 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
|
||||
Reference in New Issue
Block a user