"""Integration tests for movers tools."""
import pytest
import respx
from httpx import Response
from schwab_mcp.tools import movers
# Sample API responses (mirroring Schwab mover payloads)
MOVERS_RESPONSE = [
{
"symbol": "NVDA",
"description": "NVIDIA Corporation",
"last": 485.50,
"change": 25.30,
"percentChange": 5.50,
"totalVolume": 80000000,
"direction": "up",
"marketShare": 0.15,
"trades": 500000,
},
{
"symbol": "AMD",
"description": "Advanced Micro Devices Inc",
"last": 145.20,
"change": 7.50,
"percentChange": 5.45,
"totalVolume": 65000000,
"direction": "up",
"marketShare": 0.12,
"trades": 400000,
},
{
"symbol": "TSLA",
"description": "Tesla Inc",
"last": 252.00,
"change": 12.00,
"percentChange": 5.00,
"totalVolume": 95000000,
"direction": "up",
"marketShare": 0.18,
"trades": 600000,
},
]
MOVERS_LOSERS_RESPONSE = [
{
"symbol": "XYZ",
"description": "XYZ Corp",
"last": 50.00,
"change": -5.00,
"percentChange": -9.09,
"totalVolume": 10000000,
"direction": "down",
},
{
"symbol": "ABC",
"description": "ABC Inc",
"last": 25.00,
"change": -2.00,
"percentChange": -7.41,
"totalVolume": 5000000,
"direction": "down",
},
]
MOVERS_SCREENERS_RESPONSE = {"screeners": MOVERS_RESPONSE}
MOVERS_EMPTY_RESPONSE = []
class TestGetMovers:
"""Tests for get_movers tool."""
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_default(self, mock_client):
"""Test getting movers with default parameters."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$DJI").mock(
return_value=Response(200, json=MOVERS_RESPONSE)
)
result = await movers.get_movers(mock_client, {})
assert result["index"] == "$DJI"
assert result["direction"] == "up"
assert result["change"] == "percent"
assert result["count"] == 3
# Check first mover
nvda = result["movers"][0]
assert nvda["symbol"] == "NVDA"
assert nvda["description"] == "NVIDIA Corporation"
assert nvda["last_price"] == 485.50
assert nvda["change"] == 25.30
assert nvda["change_percent"] == 5.50
assert nvda["volume"] == 80000000
assert nvda["market_share"] == 0.15
assert nvda["trades"] == 500000
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_nasdaq(self, mock_client):
"""Test getting movers for NASDAQ composite."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$COMPX").mock(
return_value=Response(200, json=MOVERS_RESPONSE)
)
result = await movers.get_movers(mock_client, {"index": "$COMPX"})
assert result["index"] == "$COMPX"
assert result["count"] == 3
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_sp500(self, mock_client):
"""Test getting movers for S&P 500."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$SPX").mock(
return_value=Response(200, json=MOVERS_RESPONSE)
)
result = await movers.get_movers(mock_client, {"index": "$SPX"})
assert result["index"] == "$SPX"
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_losers(self, mock_client):
"""Test getting top losers."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$DJI").mock(
return_value=Response(200, json=MOVERS_LOSERS_RESPONSE)
)
result = await movers.get_movers(
mock_client, {"direction": "down"}
)
assert result["direction"] == "down"
assert result["count"] == 2
# Check loser data
xyz = result["movers"][0]
assert xyz["symbol"] == "XYZ"
assert xyz["change"] == -5.00
assert xyz["change_percent"] == -9.09
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_change_value(self, mock_client):
"""Test getting movers ranked by value change."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$DJI").mock(
return_value=Response(200, json=MOVERS_RESPONSE)
)
result = await movers.get_movers(mock_client, {"change": "value"})
assert result["change"] == "value"
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_screeners_response(self, mock_client):
"""Test handling legacy screeners response shape."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$DJI").mock(
return_value=Response(200, json=MOVERS_SCREENERS_RESPONSE)
)
result = await movers.get_movers(mock_client, {})
assert result["count"] == 3
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_empty(self, mock_client):
"""Test handling no movers found."""
respx.get("https://api.schwabapi.com/marketdata/v1/movers/$DJI").mock(
return_value=Response(200, json=MOVERS_EMPTY_RESPONSE)
)
result = await movers.get_movers(mock_client, {})
assert result["count"] == 0
assert result["movers"] == []
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_equity_all(self, mock_client):
"""Test getting movers for all equities."""
respx.get(
"https://api.schwabapi.com/marketdata/v1/movers/EQUITY_ALL"
).mock(return_value=Response(200, json=MOVERS_RESPONSE))
result = await movers.get_movers(mock_client, {"index": "EQUITY_ALL"})
assert result["index"] == "EQUITY_ALL"
assert result["count"] == 3
@respx.mock
@pytest.mark.asyncio
async def test_get_movers_options(self, mock_client):
"""Test getting movers for options."""
respx.get(
"https://api.schwabapi.com/marketdata/v1/movers/OPTION_CALL"
).mock(return_value=Response(200, json=MOVERS_RESPONSE))
result = await movers.get_movers(mock_client, {"index": "OPTION_CALL"})
assert result["index"] == "OPTION_CALL"