"""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