"""Integration tests for price history tools."""
import pytest
import respx
from httpx import Response
from schwab_mcp.tools import history
# Sample API response
PRICE_HISTORY_RESPONSE = {
"symbol": "AAPL",
"previousClose": 183.00,
"previousCloseDate": 1703203200000,
"candles": [
{
"datetime": 1703116800000, # 2023-12-21
"open": 182.00,
"high": 185.00,
"low": 181.50,
"close": 184.00,
"volume": 40000000,
},
{
"datetime": 1703203200000, # 2023-12-22
"open": 184.00,
"high": 186.00,
"low": 183.50,
"close": 185.50,
"volume": 45000000,
},
{
"datetime": 1703289600000, # 2023-12-23
"open": 185.50,
"high": 187.00,
"low": 185.00,
"close": 186.00,
"volume": 35000000,
},
],
}
class TestGetPriceHistory:
"""Tests for get_price_history tool."""
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_default(self, mock_client):
"""Test getting price history with default parameters."""
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=PRICE_HISTORY_RESPONSE)
)
result = await history.get_price_history(
mock_client, {"symbol": "AAPL"}
)
assert result["symbol"] == "AAPL"
assert result["period_type"] == "year"
assert result["period"] == 1
assert result["frequency_type"] == "daily"
assert result["frequency"] == 1
assert result["previous_close"] == 183.00
assert result["candle_count"] == 3
# Check candles are parsed correctly
candles = result["candles"]
assert len(candles) == 3
# First candle
assert candles[0]["open"] == 182.00
assert candles[0]["high"] == 185.00
assert candles[0]["low"] == 181.50
assert candles[0]["close"] == 184.00
assert candles[0]["volume"] == 40000000
# Check datetime is converted to ISO format (date varies by timezone)
assert "2023-12-2" in candles[0]["datetime"] # Could be 20 or 21 depending on TZ
assert "T" in candles[0]["datetime"] # Confirm ISO format
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_custom_period(self, mock_client):
"""Test getting price history with custom period."""
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=PRICE_HISTORY_RESPONSE)
)
result = await history.get_price_history(
mock_client,
{
"symbol": "AAPL",
"period_type": "month",
"period": 6,
"frequency_type": "weekly",
"frequency": 1,
},
)
assert result["period_type"] == "month"
assert result["period"] == 6
assert result["frequency_type"] == "weekly"
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_with_dates(self, mock_client):
"""Test getting price history with date range."""
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=PRICE_HISTORY_RESPONSE)
)
result = await history.get_price_history(
mock_client,
{
"symbol": "AAPL",
"start_date": "2023-12-01",
"end_date": "2023-12-31",
},
)
assert result["symbol"] == "AAPL"
assert result["candle_count"] == 3
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_extended_hours(self, mock_client):
"""Test getting price history with extended hours."""
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=PRICE_HISTORY_RESPONSE)
)
result = await history.get_price_history(
mock_client,
{"symbol": "AAPL", "extended_hours": True},
)
assert result["symbol"] == "AAPL"
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_empty(self, mock_client):
"""Test handling empty price history."""
empty_response = {
"symbol": "AAPL",
"candles": [],
}
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=empty_response)
)
result = await history.get_price_history(
mock_client, {"symbol": "AAPL"}
)
assert result["candles"] == []
assert result["candle_count"] == 0
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_sorted_chronologically(self, mock_client):
"""Test that candles are sorted chronologically (oldest first)."""
# Response with candles in reverse order
reversed_response = {
"symbol": "AAPL",
"candles": [
{"datetime": 1703289600000, "open": 185.50, "close": 186.00},
{"datetime": 1703116800000, "open": 182.00, "close": 184.00},
{"datetime": 1703203200000, "open": 184.00, "close": 185.50},
],
}
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=reversed_response)
)
result = await history.get_price_history(
mock_client, {"symbol": "AAPL"}
)
# Candles should be sorted by datetime
datetimes = [c["datetime"] for c in result["candles"]]
assert datetimes == sorted(datetimes)
@respx.mock
@pytest.mark.asyncio
async def test_get_price_history_lowercase_symbol(self, mock_client):
"""Test that lowercase symbols are converted to uppercase."""
respx.get("https://api.schwabapi.com/marketdata/v1/pricehistory").mock(
return_value=Response(200, json=PRICE_HISTORY_RESPONSE)
)
result = await history.get_price_history(
mock_client, {"symbol": "aapl"}
)
assert result["symbol"] == "AAPL"