use asterdex_sdk::{BookTickerShape, FuturesClient, KlineInterval, MarkPriceShape, Ticker24hrShape, TickerPriceShape};
fn public_client() -> FuturesClient {
let _ = dotenvy::dotenv();
FuturesClient::new_public("https://fapi.asterdex-testnet.com")
.expect("failed to build public client")
}
#[tokio::test]
#[ignore]
async fn test_exchange_info_has_btcusdt() {
let client = public_client();
let resp = client.get_exchange_info().await.expect("get_exchange_info failed");
assert!(!resp.data.symbols.is_empty(), "symbols list is empty");
assert!(
resp.data.symbols.iter().any(|s| s.symbol == "BTCUSDT"),
"BTCUSDT not found in exchange info"
);
assert!(!resp.data.timezone.is_empty(), "timezone is empty");
assert!(resp.data.server_time > 0, "server_time is zero");
}
#[tokio::test]
#[ignore]
async fn test_depth_btcusdt_valid_spread() {
let client = public_client();
let resp = client.get_depth("BTCUSDT", Some(5)).await.expect("get_depth failed");
assert!(resp.data.last_update_id > 0, "last_update_id is zero");
assert!(!resp.data.bids.is_empty(), "bids is empty");
assert!(!resp.data.asks.is_empty(), "asks is empty");
let best_bid: f64 = resp.data.bids[0][0].parse().expect("bid price is not f64");
let best_ask: f64 = resp.data.asks[0][0].parse().expect("ask price is not f64");
assert!(best_bid > 0.0 && best_ask > 0.0, "prices must be positive");
assert!(best_bid < best_ask, "bid {best_bid} >= ask {best_ask} — invalid spread");
}
#[tokio::test]
#[ignore]
async fn test_recent_trades_btcusdt() {
let client = public_client();
let resp = client.get_trades("BTCUSDT", Some(5)).await.expect("get_trades failed");
assert!(!resp.data.is_empty(), "recent trades is empty");
let t = &resp.data[0];
let price: f64 = t.price.parse().expect("trade price not f64");
assert!(price > 0.0, "trade price must be positive");
assert!(t.time > 0, "trade time is zero");
}
#[tokio::test]
#[ignore]
async fn test_historical_trades_btcusdt() {
let client = public_client();
match client.get_historical_trades("BTCUSDT", Some(5), None).await {
Ok(r) => {
if let Some(t) = r.data.first() {
assert!(t.id > 0);
assert!(!t.price.is_empty());
}
}
Err(e) => {
eprintln!("get_historical_trades returned error (may require API-key auth): {e:?}");
}
}
}
#[tokio::test]
#[ignore]
async fn test_agg_trades_btcusdt() {
let client = public_client();
let resp = client
.get_agg_trades("BTCUSDT", None, None, None, Some(5))
.await
.expect("get_agg_trades failed");
assert!(!resp.data.is_empty(), "agg_trades is empty");
let first = &resp.data[0];
assert!(first.agg_trade_id > 0, "agg_trade_id must be positive");
let price: f64 = first.price.parse().expect("agg trade price not f64");
assert!(price > 0.0, "agg trade price must be positive");
let qty: f64 = first.quantity.parse().expect("agg trade quantity not f64");
assert!(qty > 0.0, "agg trade quantity must be positive");
}
#[tokio::test]
#[ignore]
async fn test_klines_1h_btcusdt() {
let client = public_client();
let resp = client
.get_klines("BTCUSDT", KlineInterval::OneHour, None, None, Some(5))
.await
.expect("get_klines failed");
assert!(!resp.data.is_empty(), "klines is empty");
let k = &resp.data[0];
assert!(k.open_time > 0, "open_time is zero");
assert!(k.close_time > k.open_time, "close_time <= open_time");
let open: f64 = k.open.parse().expect("open price not f64");
let high: f64 = k.high.parse().expect("high price not f64");
let low: f64 = k.low.parse().expect("low price not f64");
let close: f64 = k.close.parse().expect("close price not f64");
assert!(open > 0.0 && high > 0.0 && low > 0.0 && close > 0.0);
assert!(high >= low, "high {high} < low {low}");
}
#[tokio::test]
#[ignore]
async fn test_mark_price_klines_btcusdt() {
let client = public_client();
let resp = client
.get_mark_price_klines("BTCUSDT", KlineInterval::OneHour, None, None, Some(5))
.await
.expect("get_mark_price_klines failed");
assert!(!resp.data.is_empty(), "mark_price_klines is empty");
assert!(resp.data[0].open_time > 0);
let open: f64 = resp.data[0].open.parse().expect("open not f64");
assert!(open > 0.0);
}
#[tokio::test]
#[ignore]
async fn test_index_price_klines_btcusdt() {
let client = public_client();
let resp = client
.get_index_price_klines("BTCUSDT", KlineInterval::OneHour, None, None, Some(5))
.await
.expect("get_index_price_klines failed");
assert!(!resp.data.is_empty(), "index_price_klines is empty");
assert!(resp.data[0].open_time > 0);
}
#[tokio::test]
#[ignore]
async fn test_mark_price_single_btcusdt() {
let client = public_client();
let resp = client
.get_mark_price(Some("BTCUSDT"))
.await
.expect("get_mark_price(BTCUSDT) failed");
match resp.data {
MarkPriceShape::Single(mp) => {
assert_eq!(mp.symbol, "BTCUSDT");
let price: f64 = mp.mark_price.parse().expect("mark_price not f64");
assert!(price > 0.0, "mark_price must be positive");
assert!(mp.next_funding_time > 0);
}
MarkPriceShape::Multi(_) => panic!("expected Single for BTCUSDT, got Multi"),
}
}
#[tokio::test]
#[ignore]
async fn test_mark_price_all_symbols() {
let client = public_client();
let resp = client
.get_mark_price(None)
.await
.expect("get_mark_price(None) failed");
match resp.data {
MarkPriceShape::Multi(prices) => {
assert!(!prices.is_empty(), "mark prices list is empty");
assert!(
prices.iter().any(|mp| mp.symbol == "BTCUSDT"),
"BTCUSDT not in multi mark prices"
);
}
MarkPriceShape::Single(_) => panic!("expected Multi for all symbols, got Single"),
}
}
#[tokio::test]
#[ignore]
async fn test_funding_rate_btcusdt() {
let client = public_client();
match client.get_funding_rate(Some("BTCUSDT"), None, None, Some(5)).await {
Ok(resp) => {
assert!(!resp.data.is_empty(), "funding_rate history is empty");
let r = &resp.data[0];
assert_eq!(r.symbol, "BTCUSDT");
assert!(r.funding_time > 0, "funding_time is zero");
}
Err(asterdex_sdk::AsterDexError::Timeout { .. }) => {
eprintln!("get_funding_rate timed out on testnet — skipping");
}
Err(e) => panic!("get_funding_rate failed: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_funding_info_non_empty() {
let client = public_client();
let resp = client.get_funding_info().await.expect("get_funding_info failed");
assert!(!resp.data.is_empty(), "funding info is empty");
let first = &resp.data[0];
assert!(!first.symbol.is_empty(), "symbol is empty");
if let Some(hours) = first.funding_interval_hours {
assert!(hours > 0, "funding_interval_hours is zero");
}
}
#[tokio::test]
#[ignore]
async fn test_ticker_24hr_btcusdt() {
let client = public_client();
let resp = client
.get_ticker_24hr(Some("BTCUSDT"))
.await
.expect("get_ticker_24hr failed");
match resp.data {
Ticker24hrShape::Single(t) => {
assert_eq!(t.symbol, "BTCUSDT");
let last_price: f64 = t.last_price.parse().expect("last_price not f64");
assert!(last_price > 0.0, "last_price must be positive");
assert!(t.close_time > t.open_time);
}
Ticker24hrShape::Multi(_) => panic!("expected Single for BTCUSDT, got Multi"),
}
}
#[tokio::test]
#[ignore]
async fn test_ticker_price_btcusdt() {
let client = public_client();
let resp = client
.get_ticker_price(Some("BTCUSDT"))
.await
.expect("get_ticker_price failed");
match resp.data {
TickerPriceShape::Single(t) => {
assert_eq!(t.symbol, "BTCUSDT");
let price: f64 = t.price.parse().expect("ticker price not f64");
assert!(price > 0.0, "ticker price must be positive");
}
TickerPriceShape::Multi(_) => panic!("expected Single for BTCUSDT, got Multi"),
}
}
#[tokio::test]
#[ignore]
async fn test_book_ticker_btcusdt_valid_spread() {
let client = public_client();
let resp = client
.get_book_ticker(Some("BTCUSDT"))
.await
.expect("get_book_ticker failed");
match resp.data {
BookTickerShape::Single(b) => {
assert_eq!(b.symbol, "BTCUSDT");
assert!(!b.bid_price.is_empty(), "bidPrice missing from book ticker");
assert!(!b.ask_price.is_empty(), "askPrice missing from book ticker");
let bid_f: f64 = b.bid_price.parse().expect("bidPrice not f64");
let ask_f: f64 = b.ask_price.parse().expect("askPrice not f64");
assert!(bid_f > 0.0 && ask_f > 0.0);
assert!(bid_f < ask_f, "bid {bid_f} >= ask {ask_f} — invalid book ticker");
}
BookTickerShape::Multi(_) => panic!("expected Single for BTCUSDT, got Multi"),
}
}
#[tokio::test]
#[ignore]
async fn test_index_references_btcusdt() {
let client = public_client();
match client.get_index_references(Some("BTCUSDT")).await {
Ok(resp) => assert!(!resp.data.is_empty() || resp.data.is_empty(), "index references returned unexpected result"),
Err(asterdex_sdk::AsterDexError::ApiError { code, msg }) => {
eprintln!("get_index_references returned ApiError {code}: {msg} — endpoint may not be available");
}
Err(e) => panic!("unexpected error from get_index_references: {e:?}"),
}
}