mod helpers;
use tradestation_api::BarChartQuery;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};
#[tokio::test]
async fn test_get_bars() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/barcharts/AAPL"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"Bars": [
{
"High": "185.50",
"Low": "184.20",
"Open": "184.50",
"Close": "185.30",
"TimeStamp": "2026-03-25T14:30:00Z",
"TotalVolume": "1250000",
"DownTicks": 120,
"DownVolume": 400000,
"TotalTicks": 300,
"UpTicks": 180,
"UpVolume": 850000
},
{
"High": "185.80",
"Low": "185.10",
"Open": "185.30",
"Close": "185.60",
"TimeStamp": "2026-03-25T14:31:00Z",
"TotalVolume": "980000"
}
]
})))
.mount(&server)
.await;
let query = BarChartQuery::minute_bars("AAPL", 2);
let bars = client.get_bars(&query).await.unwrap();
assert_eq!(bars.len(), 2);
assert_eq!(bars[0].close, "185.30");
assert_eq!(bars[0].total_volume, "1250000");
assert_eq!(bars[1].open, "185.30");
let (o, h, l, c, v) = bars[0].ohlcv().unwrap();
assert!((o - 184.50).abs() < 0.01);
assert!((h - 185.50).abs() < 0.01);
assert!((l - 184.20).abs() < 0.01);
assert!((c - 185.30).abs() < 0.01);
assert_eq!(v, 1250000);
}
#[tokio::test]
async fn test_get_quotes() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/quotes/AAPL,MSFT"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"Quotes": [
{
"Symbol": "AAPL",
"Last": "185.50",
"Ask": "185.55",
"Bid": "185.45",
"Volume": "45000000",
"Close": "184.80",
"High": "186.00",
"Low": "184.20",
"Open": "184.90",
"NetChange": "0.70",
"NetChangePct": "0.38",
"TradeTime": "2026-03-25T20:00:00Z"
},
{
"Symbol": "MSFT",
"Last": "420.10",
"Ask": "420.15",
"Bid": "420.05",
"Volume": "22000000"
}
]
})))
.mount(&server)
.await;
let quotes = client.get_quotes(&["AAPL", "MSFT"]).await.unwrap();
assert_eq!(quotes.len(), 2);
assert_eq!(quotes[0].symbol, "AAPL");
assert_eq!(quotes[0].last, "185.50");
assert_eq!(quotes[0].ask, "185.55");
assert_eq!(quotes[0].bid, "185.45");
assert_eq!(quotes[0].net_change.as_deref(), Some("0.70"));
assert_eq!(quotes[1].symbol, "MSFT");
assert_eq!(quotes[1].last, "420.10");
}
#[tokio::test]
async fn test_get_symbol_info() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/symbols/AAPL"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"Definitions": [
{
"Symbol": "AAPL",
"Description": "Apple Inc",
"Exchange": "NASDAQ",
"Category": "Stock",
"Currency": "USD",
"PointValue": "1",
"MinMove": "0.01"
}
]
})))
.mount(&server)
.await;
let symbols = client.get_symbol_info(&["AAPL"]).await.unwrap();
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].symbol, "AAPL");
assert_eq!(symbols[0].description.as_deref(), Some("Apple Inc"));
assert_eq!(symbols[0].exchange.as_deref(), Some("NASDAQ"));
assert_eq!(symbols[0].category.as_deref(), Some("Stock"));
assert_eq!(symbols[0].currency.as_deref(), Some("USD"));
}
#[tokio::test]
async fn test_get_crypto_pairs() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/symbollists/cryptopairs/symbolnames"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"CryptoPairs": ["BTC/USD", "ETH/USD", "LTC/USD"]
})))
.mount(&server)
.await;
let pairs = client.get_crypto_pairs().await.unwrap();
assert_eq!(pairs.len(), 3);
assert_eq!(pairs[0], "BTC/USD");
assert_eq!(pairs[1], "ETH/USD");
assert_eq!(pairs[2], "LTC/USD");
}
#[tokio::test]
async fn test_get_option_expirations() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/options/expirations/AAPL"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"Expirations": [
{ "Date": "2026-04-17", "Type": "Monthly" },
{ "Date": "2026-04-24", "Type": "Weekly" }
]
})))
.mount(&server)
.await;
let expirations = client.get_option_expirations("AAPL").await.unwrap();
assert_eq!(expirations.len(), 2);
assert_eq!(expirations[0].date, "2026-04-17");
assert_eq!(expirations[0].expiration_type.as_deref(), Some("Monthly"));
assert_eq!(expirations[1].date, "2026-04-24");
assert_eq!(expirations[1].expiration_type.as_deref(), Some("Weekly"));
}
#[tokio::test]
async fn test_get_option_strikes() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/options/strikes/AAPL"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"Strikes": [
{ "StrikePrice": "150.00" },
{ "StrikePrice": "155.00" },
{ "StrikePrice": "160.00" }
]
})))
.mount(&server)
.await;
let strikes = client.get_option_strikes("AAPL").await.unwrap();
assert_eq!(strikes.len(), 3);
assert_eq!(strikes[0].strike_price, "150.00");
assert_eq!(strikes[1].strike_price, "155.00");
assert_eq!(strikes[2].strike_price, "160.00");
}
#[tokio::test]
async fn test_get_option_spread_types() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("GET"))
.and(path("/v3/marketdata/options/spreadtypes"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"SpreadTypes": [
{ "Name": "Vertical", "Description": "Vertical spread" },
{ "Name": "Calendar", "Description": "Calendar spread" }
]
})))
.mount(&server)
.await;
let spread_types = client.get_option_spread_types().await.unwrap();
assert_eq!(spread_types.len(), 2);
assert_eq!(spread_types[0].name, "Vertical");
assert_eq!(
spread_types[0].description.as_deref(),
Some("Vertical spread")
);
assert_eq!(spread_types[1].name, "Calendar");
}
#[tokio::test]
async fn test_get_option_risk_reward() {
let server = helpers::setup_mock_server().await;
let mut client = helpers::mock_client(&server);
Mock::given(method("POST"))
.and(path("/v3/marketdata/options/riskreward"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"MaxReward": "500.00",
"MaxRisk": "250.00",
"BreakEven": "185.00"
})))
.mount(&server)
.await;
let request = tradestation_api::RiskRewardRequest {
symbol: "AAPL".to_string(),
trade_action: "Buy".to_string(),
quantity: "1".to_string(),
limit_price: Some("185.00".to_string()),
stop_price: None,
};
let result = client.get_option_risk_reward(&request).await.unwrap();
assert_eq!(result.max_reward.as_deref(), Some("500.00"));
assert_eq!(result.max_risk.as_deref(), Some("250.00"));
assert_eq!(result.break_even.as_deref(), Some("185.00"));
}