"""Tests for shorts and dark pool 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 # --- Short Volume --- @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_volume", new_callable=AsyncMock) async def test_short_volume_happy_path(mock_fn, client): mock_fn.return_value = [ {"date": "2026-03-18", "short_volume": 5000000, "short_exempt_volume": 10000, "total_volume": 20000000, "short_volume_percent": 0.25} ] resp = await client.get("/api/v1/stock/AAPL/shorts/volume") assert resp.status_code == 200 data = resp.json() assert data["success"] is True assert len(data["data"]) == 1 assert data["data"][0]["short_volume"] == 5000000 @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_volume", new_callable=AsyncMock) async def test_short_volume_empty(mock_fn, client): mock_fn.return_value = [] resp = await client.get("/api/v1/stock/GME/shorts/volume") assert resp.status_code == 200 data = resp.json() assert data["success"] is True assert data["data"] == [] @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_volume", new_callable=AsyncMock) async def test_short_volume_service_error_returns_502(mock_fn, client): mock_fn.side_effect = RuntimeError("stockgrid unavailable") resp = await client.get("/api/v1/stock/AAPL/shorts/volume") assert resp.status_code == 502 @pytest.mark.asyncio async def test_short_volume_invalid_symbol(client): resp = await client.get("/api/v1/stock/AAPL;DROP/shorts/volume") assert resp.status_code == 400 # --- Fails To Deliver --- @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_fails_to_deliver", new_callable=AsyncMock) async def test_ftd_happy_path(mock_fn, client): mock_fn.return_value = [ {"date": "2026-03-01", "cusip": "037833100", "failure_quantity": 50000, "symbol": "AAPL", "price": 175.0} ] resp = await client.get("/api/v1/stock/AAPL/shorts/ftd") assert resp.status_code == 200 data = resp.json() assert data["success"] is True assert data["data"][0]["symbol"] == "AAPL" assert data["data"][0]["failure_quantity"] == 50000 @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_fails_to_deliver", new_callable=AsyncMock) async def test_ftd_empty(mock_fn, client): mock_fn.return_value = [] resp = await client.get("/api/v1/stock/TSLA/shorts/ftd") assert resp.status_code == 200 assert resp.json()["data"] == [] @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_fails_to_deliver", new_callable=AsyncMock) async def test_ftd_service_error_returns_502(mock_fn, client): mock_fn.side_effect = RuntimeError("SEC connection failed") resp = await client.get("/api/v1/stock/AAPL/shorts/ftd") assert resp.status_code == 502 @pytest.mark.asyncio async def test_ftd_invalid_symbol(client): resp = await client.get("/api/v1/stock/BAD!!!/shorts/ftd") assert resp.status_code == 400 # --- Short Interest --- @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_interest", new_callable=AsyncMock) async def test_short_interest_happy_path(mock_fn, client): mock_fn.return_value = [ {"settlement_date": "2026-02-28", "symbol": "GME", "short_interest": 20000000, "days_to_cover": 3.5} ] resp = await client.get("/api/v1/stock/GME/shorts/interest") assert resp.status_code == 200 data = resp.json() assert data["success"] is True assert data["data"][0]["short_interest"] == 20000000 assert data["data"][0]["days_to_cover"] == 3.5 @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_interest", new_callable=AsyncMock) async def test_short_interest_empty(mock_fn, client): mock_fn.return_value = [] resp = await client.get("/api/v1/stock/NVDA/shorts/interest") assert resp.status_code == 200 assert resp.json()["data"] == [] @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_short_interest", new_callable=AsyncMock) async def test_short_interest_service_error_returns_502(mock_fn, client): mock_fn.side_effect = RuntimeError("FINRA unavailable") resp = await client.get("/api/v1/stock/AAPL/shorts/interest") assert resp.status_code == 502 @pytest.mark.asyncio async def test_short_interest_invalid_symbol(client): resp = await client.get("/api/v1/stock/INVALID!!!/shorts/interest") assert resp.status_code == 400 # --- Dark Pool OTC --- @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_darkpool_otc", new_callable=AsyncMock) async def test_darkpool_otc_happy_path(mock_fn, client): mock_fn.return_value = [ {"date": "2026-03-18", "symbol": "AAPL", "shares": 3000000, "percentage": 12.5} ] resp = await client.get("/api/v1/darkpool/AAPL/otc") assert resp.status_code == 200 data = resp.json() assert data["success"] is True assert data["data"][0]["percentage"] == 12.5 @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_darkpool_otc", new_callable=AsyncMock) async def test_darkpool_otc_empty(mock_fn, client): mock_fn.return_value = [] resp = await client.get("/api/v1/darkpool/TSLA/otc") assert resp.status_code == 200 assert resp.json()["data"] == [] @pytest.mark.asyncio @patch("routes_shorts.shorts_service.get_darkpool_otc", new_callable=AsyncMock) async def test_darkpool_otc_service_error_returns_502(mock_fn, client): mock_fn.side_effect = RuntimeError("FINRA connection timeout") resp = await client.get("/api/v1/darkpool/AAPL/otc") assert resp.status_code == 502 @pytest.mark.asyncio async def test_darkpool_otc_invalid_symbol(client): resp = await client.get("/api/v1/darkpool/BAD!!!/otc") assert resp.status_code == 400