"""Integration tests for quote tools."""
import pytest
import respx
from httpx import Response
from schwab_mcp.tools import quotes
# Sample API responses
QUOTE_RESPONSE = {
"AAPL": {
"assetMainType": "EQUITY",
"quote": {
"lastPrice": 185.50,
"bidPrice": 185.45,
"askPrice": 185.55,
"bidSize": 200,
"askSize": 150,
"totalVolume": 45000000,
"highPrice": 187.00,
"lowPrice": 184.00,
"openPrice": 184.50,
"closePrice": 183.00,
"netChange": 2.50,
"netPercentChange": 1.37,
"52WeekHigh": 199.62,
"52WeekLow": 164.08,
"peRatio": 31.25,
"divYield": 0.49,
},
"reference": {
"marketCap": 2850000000000,
"exchange": "NASDAQ",
"description": "Apple Inc - Common Stock",
},
}
}
MULTI_QUOTE_RESPONSE = {
"AAPL": QUOTE_RESPONSE["AAPL"],
"MSFT": {
"assetMainType": "EQUITY",
"quote": {
"lastPrice": 420.00,
"bidPrice": 419.90,
"askPrice": 420.10,
"totalVolume": 20000000,
"netChange": 5.00,
"netPercentChange": 1.20,
},
"reference": {
"exchange": "NASDAQ",
"description": "Microsoft Corporation",
},
},
}
class TestGetQuote:
"""Tests for get_quote tool."""
@respx.mock
@pytest.mark.asyncio
async def test_get_quote_success(self, mock_client):
"""Test getting a single quote."""
respx.get("https://api.schwabapi.com/marketdata/v1/quotes").mock(
return_value=Response(200, json=QUOTE_RESPONSE)
)
result = await quotes.get_quote(mock_client, {"symbol": "AAPL"})
assert result["symbol"] == "AAPL"
assert result["asset_type"] == "EQUITY"
assert result["last_price"] == 185.50
assert result["bid"] == 185.45
assert result["ask"] == 185.55
assert result["volume"] == 45000000
assert result["day_change"] == 2.50
assert result["day_change_percent"] == 1.37
assert result["52_week_high"] == 199.62
assert result["52_week_low"] == 164.08
assert result["pe_ratio"] == 31.25
assert result["div_yield"] == 0.49
assert result["market_cap"] == 2850000000000
assert result["exchange"] == "NASDAQ"
@respx.mock
@pytest.mark.asyncio
async def test_get_quote_lowercase_symbol(self, mock_client):
"""Test that lowercase symbols are converted to uppercase."""
respx.get("https://api.schwabapi.com/marketdata/v1/quotes").mock(
return_value=Response(200, json=QUOTE_RESPONSE)
)
result = await quotes.get_quote(mock_client, {"symbol": "aapl"})
assert result["symbol"] == "AAPL"
class TestGetQuotes:
"""Tests for get_quotes tool."""
@respx.mock
@pytest.mark.asyncio
async def test_get_quotes_multiple(self, mock_client):
"""Test getting multiple quotes."""
respx.get("https://api.schwabapi.com/marketdata/v1/quotes").mock(
return_value=Response(200, json=MULTI_QUOTE_RESPONSE)
)
result = await quotes.get_quotes(
mock_client, {"symbols": ["AAPL", "MSFT"]}
)
assert len(result["quotes"]) == 2
aapl = result["quotes"][0]
assert aapl["symbol"] == "AAPL"
assert aapl["last_price"] == 185.50
msft = result["quotes"][1]
assert msft["symbol"] == "MSFT"
assert msft["last_price"] == 420.00
@respx.mock
@pytest.mark.asyncio
async def test_get_quotes_symbol_not_found(self, mock_client):
"""Test handling of symbol not found in response."""
# Response only contains AAPL, not INVALID
respx.get("https://api.schwabapi.com/marketdata/v1/quotes").mock(
return_value=Response(200, json=QUOTE_RESPONSE)
)
result = await quotes.get_quotes(
mock_client, {"symbols": ["AAPL", "INVALID"]}
)
assert len(result["quotes"]) == 2
aapl = result["quotes"][0]
assert aapl["symbol"] == "AAPL"
assert aapl["last_price"] == 185.50
invalid = result["quotes"][1]
assert invalid["symbol"] == "INVALID"
assert invalid["error"] == "Symbol not found"
@respx.mock
@pytest.mark.asyncio
async def test_get_quotes_empty_list(self, mock_client):
"""Test getting quotes with empty symbol list."""
respx.get("https://api.schwabapi.com/marketdata/v1/quotes").mock(
return_value=Response(200, json={})
)
result = await quotes.get_quotes(mock_client, {"symbols": []})
assert result["quotes"] == []