"""Integration tests for instruments tools."""
import pytest
import respx
from httpx import Response
from schwab_mcp.tools import instruments
# Sample API responses
INSTRUMENTS_SEARCH_RESPONSE = {
"instruments": [
{
"symbol": "AAPL",
"cusip": "037833100",
"description": "Apple Inc - Common Stock",
"exchange": "NASDAQ",
"assetType": "EQUITY",
},
{
"symbol": "AAPD",
"cusip": "00768Y883",
"description": "Direxion Daily AAPL Bear 1X Shares",
"exchange": "NASDAQ",
"assetType": "ETF",
},
]
}
INSTRUMENTS_FUNDAMENTAL_RESPONSE = {
"instruments": [
{
"symbol": "AAPL",
"cusip": "037833100",
"description": "Apple Inc - Common Stock",
"exchange": "NASDAQ",
"assetType": "EQUITY",
"fundamental": {
"peRatio": 31.25,
"pegRatio": 2.50,
"pbRatio": 45.00,
"divYield": 0.49,
"divAmount": 0.96,
"divDate": "2024-02-09",
"divPayDate": "2024-02-15",
"eps": 6.05,
"week52High": 199.62,
"week52Low": 164.08,
"marketCap": 2850000000000,
"avg10DaysVolume": 55000000,
"avg1YearVolume": 60000000,
"sharesOutstanding": 15400000000,
"beta": 1.25,
"nextDivDate": "2024-05-10",
"nextDivPayDate": "2024-05-16",
},
}
]
}
INSTRUMENTS_EMPTY_RESPONSE = {"instruments": []}
class TestGetInstruments:
"""Tests for get_instruments tool."""
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_symbol_search(self, mock_client):
"""Test searching for instruments by symbol."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_SEARCH_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "AAP"}
)
assert result["query"] == "AAP"
assert result["projection"] == "symbol-search"
assert result["count"] == 2
# Check first instrument
aapl = result["instruments"][0]
assert aapl["symbol"] == "AAPL"
assert aapl["cusip"] == "037833100"
assert aapl["description"] == "Apple Inc - Common Stock"
assert aapl["exchange"] == "NASDAQ"
assert aapl["asset_type"] == "EQUITY"
# Check second instrument
aapd = result["instruments"][1]
assert aapd["symbol"] == "AAPD"
assert aapd["asset_type"] == "ETF"
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_fundamental(self, mock_client):
"""Test getting fundamental data for a symbol."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_FUNDAMENTAL_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "AAPL", "projection": "fundamental"}
)
assert result["query"] == "AAPL"
assert result["projection"] == "fundamental"
assert result["count"] == 1
inst = result["instruments"][0]
assert inst["symbol"] == "AAPL"
# Check fundamental data
fund = inst["fundamental"]
assert fund["pe_ratio"] == 31.25
assert fund["peg_ratio"] == 2.50
assert fund["pb_ratio"] == 45.00
assert fund["div_yield"] == 0.49
assert fund["div_amount"] == 0.96
assert fund["eps"] == 6.05
assert fund["week_52_high"] == 199.62
assert fund["week_52_low"] == 164.08
assert fund["market_cap"] == 2850000000000
assert fund["avg_volume_10_day"] == 55000000
assert fund["shares_outstanding"] == 15400000000
assert fund["beta"] == 1.25
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_desc_search(self, mock_client):
"""Test searching instruments by description."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_SEARCH_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "Apple", "projection": "desc-search"}
)
assert result["projection"] == "desc-search"
assert result["count"] == 2
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_empty_result(self, mock_client):
"""Test handling no results found."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_EMPTY_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "XYZNOTFOUND"}
)
assert result["count"] == 0
assert result["instruments"] == []
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_regex_search(self, mock_client):
"""Test searching with regex pattern."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_SEARCH_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "AAP.*", "projection": "symbol-regex"}
)
assert result["projection"] == "symbol-regex"
assert result["count"] == 2
@respx.mock
@pytest.mark.asyncio
async def test_get_instruments_lowercase_query(self, mock_client):
"""Test that query terms work with any case."""
respx.get("https://api.schwabapi.com/marketdata/v1/instruments").mock(
return_value=Response(200, json=INSTRUMENTS_SEARCH_RESPONSE)
)
result = await instruments.get_instruments(
mock_client, {"symbol": "aapl"}
)
# Query should be preserved but API handles case
assert result["query"] == "aapl"
assert result["count"] == 2