use asterdex_sdk::{
AsterDexError, CancelOrderParams, PlaceOrderParams, FuturesClient,
};
use asterdex_sdk::futures::models::trading::{
AllOrdersParams, CountdownParams, GetOrderParams, IncomeParams, UserTradesParams,
};
use asterdex_sdk::futures::models::account::MmpUpdateParams;
fn build_client() -> FuturesClient {
let _ = dotenvy::dotenv();
FuturesClient::from_env().expect("FuturesClient::from_env() failed — check .env credentials")
}
async fn place_test_order(client: &FuturesClient) -> i64 {
client
.place_order(PlaceOrderParams {
symbol: "BTCUSDT".to_string(),
side: "BUY".to_string(),
order_type: "LIMIT".to_string(),
quantity: "0.001".to_string(),
price: Some("70000.0".to_string()),
time_in_force: Some("GTC".to_string()),
..Default::default()
})
.await
.expect("place test order failed")
.data
.order_id
}
async fn cleanup_order(client: &FuturesClient, order_id: i64) {
let _ = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order_id),
..Default::default()
})
.await;
}
#[tokio::test]
#[ignore]
async fn test_get_balance() {
let client = build_client();
let resp = client.get_balance().await.expect("get_balance failed");
assert!(!resp.data.is_empty(), "balance list is empty");
let _ = &resp.data[0].asset; }
#[tokio::test]
#[ignore]
async fn test_get_account_with_join_margin() {
let client = build_client();
let resp = client
.get_account_with_join_margin()
.await
.expect("get_account_with_join_margin failed");
let _ = resp.data.total_wallet_balance;
}
#[tokio::test]
#[ignore]
async fn test_get_position_side_dual() {
let client = build_client();
let resp = client
.get_position_side_dual()
.await
.expect("get_position_side_dual failed");
let _ = resp.data.dual_side_position;
}
#[tokio::test]
#[ignore]
async fn test_set_position_side_dual_noop() {
let client = build_client();
let current = client
.get_position_side_dual()
.await
.expect("get_position_side_dual failed")
.data
.dual_side_position;
let result = client.set_position_side_dual(current).await;
match result {
Err(AsterDexError::ApiError { code: -4059, .. }) => {}
Ok(r) => assert_eq!(r.data.code, 200),
Err(e) => panic!("unexpected error from set_position_side_dual: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_get_leverage_bracket_btcusdt() {
let client = build_client();
let resp = client
.get_leverage_bracket(Some("BTCUSDT"))
.await
.expect("get_leverage_bracket failed");
assert!(!resp.data.is_empty(), "leverage brackets is empty");
let btc = resp
.data
.iter()
.find(|b| b.symbol == "BTCUSDT")
.expect("BTCUSDT not in leverage brackets");
assert!(!btc.brackets.is_empty(), "BTCUSDT has no brackets");
assert!(btc.brackets[0].initial_leverage > 0);
}
#[tokio::test]
#[ignore]
async fn test_get_commission_rate_btcusdt() {
let client = build_client();
let resp = client
.get_commission_rate("BTCUSDT")
.await
.expect("get_commission_rate failed");
assert_eq!(resp.data.symbol, "BTCUSDT");
assert!(!resp.data.maker_commission_rate.is_empty());
assert!(!resp.data.taker_commission_rate.is_empty());
}
#[tokio::test]
#[ignore]
async fn test_get_multi_assets_margin() {
let client = build_client();
let resp = client
.get_multi_assets_margin()
.await
.expect("get_multi_assets_margin failed");
let _ = resp.data.multi_assets_margin;
}
#[tokio::test]
#[ignore]
async fn test_get_adl_quantile_btcusdt() {
let client = build_client();
match client.get_adl_quantile(Some("BTCUSDT")).await {
Ok(resp) => { let _ = resp.data; }
Err(AsterDexError::SerdeError { .. }) => {
eprintln!("get_adl_quantile: empty-object response (no open positions)");
}
Err(e) => panic!("unexpected error from get_adl_quantile: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_get_positions_btcusdt() {
let client = build_client();
match client.get_position_risk(Some("BTCUSDT")).await {
Ok(resp) => { let _ = resp.data; }
Err(AsterDexError::HttpError { status: 404, .. }) => {
eprintln!("get_position_risk: 404 — endpoint not available on this testnet");
}
Err(e) => panic!("unexpected error from get_position_risk: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_get_all_orders_btcusdt() {
let client = build_client();
let resp = client
.get_all_orders(AllOrdersParams {
symbol: "BTCUSDT".to_string(),
limit: Some(5),
..Default::default()
})
.await
.expect("get_all_orders failed");
let _ = resp.data;
}
#[tokio::test]
#[ignore]
async fn test_get_order_by_id() {
let client = build_client();
let order_id = place_test_order(&client).await;
let resp = client
.get_order(GetOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order_id),
orig_client_order_id: None,
})
.await
.expect("get_order failed");
assert_eq!(resp.data.order_id, order_id);
assert_eq!(resp.data.symbol, "BTCUSDT");
cleanup_order(&client, order_id).await;
}
#[tokio::test]
#[ignore]
async fn test_get_open_order_by_id() {
let client = build_client();
let order_id = place_test_order(&client).await;
let resp = client
.get_open_order(GetOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order_id),
orig_client_order_id: None,
})
.await
.expect("get_open_order failed");
assert_eq!(resp.data.order_id, order_id);
assert_eq!(resp.data.status, "NEW");
cleanup_order(&client, order_id).await;
}
#[tokio::test]
#[ignore]
async fn test_get_user_trades_btcusdt() {
let client = build_client();
match client
.get_user_trades(UserTradesParams {
symbol: "BTCUSDT".to_string(),
limit: Some(5),
..Default::default()
})
.await
{
Ok(resp) => { let _ = resp.data; }
Err(AsterDexError::SerdeError { .. }) => {
eprintln!("get_user_trades: serde error — testnet may return public trades at this endpoint");
}
Err(e) => panic!("unexpected error from get_user_trades: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_get_income() {
let client = build_client();
let resp = client
.get_income(IncomeParams {
limit: Some(5),
..Default::default()
})
.await
.expect("get_income failed");
let _ = resp.data;
}
#[tokio::test]
#[ignore]
async fn test_get_force_orders() {
let client = build_client();
let resp = client
.get_force_orders(Some("BTCUSDT"), None, None, None, Some(5))
.await
.expect("get_force_orders failed");
let _ = resp.data;
}
#[tokio::test]
#[ignore]
async fn test_get_position_margin_history_btcusdt() {
let client = build_client();
let resp = client
.get_position_margin_history("BTCUSDT", None, None, None, Some(5))
.await
.expect("get_position_margin_history failed");
let _ = resp.data;
}
#[tokio::test]
#[ignore]
async fn test_set_leverage_btcusdt_10x() {
let client = build_client();
let resp = client
.set_leverage("BTCUSDT", 10)
.await
.expect("set_leverage failed");
assert_eq!(resp.data.symbol, "BTCUSDT");
assert_eq!(resp.data.leverage, 10);
assert!(!resp.data.max_notional_value.is_empty());
let _ = client.set_leverage("BTCUSDT", 20).await;
}
#[tokio::test]
#[ignore]
async fn test_set_margin_type_crossed_btcusdt() {
let client = build_client();
match client.set_margin_type("BTCUSDT", "CROSSED").await {
Ok(r) => assert_eq!(r.data.code, 200),
Err(AsterDexError::ApiError { code: -4046, .. }) => {}
Err(e) => panic!("unexpected error: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_noop() {
let client = build_client();
client.noop().await.expect("noop failed");
}
#[tokio::test]
#[ignore]
async fn test_countdown_cancel_all_disable() {
let client = build_client();
let resp = client
.countdown_cancel_all(CountdownParams {
symbol: "BTCUSDT".to_string(),
countdown_time: 0, })
.await
.expect("countdown_cancel_all failed");
assert_eq!(resp.data.symbol, "BTCUSDT");
assert_eq!(resp.data.countdown_time, 0);
}
#[tokio::test]
#[ignore]
async fn test_listen_key_lifecycle() {
let client = build_client();
let create_resp = client
.create_listen_key()
.await
.expect("create_listen_key failed");
let listen_key = create_resp.data.listen_key.clone();
assert!(!listen_key.is_empty(), "listen_key is empty");
client
.renew_listen_key(&listen_key)
.await
.expect("renew_listen_key failed");
client
.delete_listen_key(&listen_key)
.await
.expect("delete_listen_key failed");
}
#[tokio::test]
#[ignore]
async fn test_renew_invalid_listen_key_returns_error() {
let client = build_client();
let result = client.renew_listen_key("invalid_key_that_does_not_exist").await;
assert!(
matches!(result, Err(AsterDexError::ApiError { code: -1125, .. }))
|| result.is_err(),
"expected error for invalid listen key, got Ok"
);
}
#[tokio::test]
async fn test_place_batch_orders_btcusdt() {
let client = build_client();
let orders = vec![
PlaceOrderParams {
symbol: "BTCUSDT".to_string(),
side: "BUY".to_string(),
order_type: "LIMIT".to_string(),
quantity: "0.001".to_string(),
price: Some("70000.0".to_string()),
time_in_force: Some("GTC".to_string()),
..Default::default()
},
PlaceOrderParams {
symbol: "BTCUSDT".to_string(),
side: "BUY".to_string(),
order_type: "LIMIT".to_string(),
quantity: "0.001".to_string(),
price: Some("9900.0".to_string()),
time_in_force: Some("GTC".to_string()),
..Default::default()
},
];
let batch_resp = client.place_batch_orders(orders).await
.expect("place_batch_orders failed");
assert_eq!(batch_resp.data.len(), 2, "expected 2 batch results");
let order_ids: Vec<i64> = batch_resp
.data
.iter()
.filter_map(|r| r.order_id)
.collect();
assert_eq!(order_ids.len(), 2, "expected 2 successful order placements");
let cancel_resp = client
.cancel_batch_orders("BTCUSDT", order_ids)
.await
.expect("cancel_batch_orders failed");
assert_eq!(cancel_resp.data.len(), 2, "expected 2 cancel results");
}
#[tokio::test]
async fn test_place_batch_orders_exceeds_limit() {
let client = build_client();
let orders: Vec<PlaceOrderParams> = (0..6)
.map(|_| PlaceOrderParams::default())
.collect();
let result = client.place_batch_orders(orders).await;
assert!(
matches!(result, Err(AsterDexError::InvalidParams { .. })),
"expected InvalidParams for >5 orders, got: {result:?}"
);
}
#[tokio::test]
#[ignore]
async fn test_cancel_all_open_orders_btcusdt() {
let client = build_client();
let resp = client
.cancel_all_open_orders("BTCUSDT")
.await
.expect("cancel_all_open_orders failed");
assert_eq!(resp.data.code, 200, "expected code 200, got: {}", resp.data.code);
}
#[tokio::test]
#[ignore]
async fn test_get_mmp_btcusdt() {
let client = build_client();
match client.get_mmp(Some("BTCUSDT")).await {
Ok(resp) => assert!(resp.data.iter().any(|m| m.symbol == "BTCUSDT")),
Err(AsterDexError::ApiError { code, msg }) => {
eprintln!("get_mmp ApiError {code}: {msg}");
}
Err(AsterDexError::HttpError { status: 404, .. }) => {
eprintln!("get_mmp: 404 — MMP endpoint not available on this testnet");
}
Err(e) => panic!("unexpected error from get_mmp: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_mmp_lifecycle_btcusdt() {
let client = build_client();
let params = MmpUpdateParams {
symbol: "BTCUSDT".to_string(),
window_time_ms: 5_000,
frozen_time_ms: 3_000,
qty_limit: Some(100),
value_limit: None,
delta_limit: Some(50),
};
match client.update_mmp(params).await {
Ok(_resp) => {
let get_resp = client
.get_mmp(Some("BTCUSDT"))
.await
.expect("get_mmp after update failed");
assert_eq!(get_resp.data[0].symbol, "BTCUSDT");
let _ = client.delete_mmp("BTCUSDT").await;
}
Err(AsterDexError::ApiError { code, msg }) => {
eprintln!("update_mmp ApiError {code}: {msg} — MMP may not be available");
}
Err(AsterDexError::HttpError { status: 404, .. }) => {
eprintln!("update_mmp: 404 — MMP endpoint not available on this testnet");
}
Err(e) => panic!("unexpected error from update_mmp: {e:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_raw_signed_post_noop() {
let client = build_client();
let resp = client
.raw_signed_post("/fapi/v3/noop", &[])
.await
.expect("raw_signed_post noop failed");
assert!(
resp.data.is_object() || resp.data.is_null(),
"expected object or null, got: {:?}",
resp.data
);
}
#[tokio::test]
#[ignore]
async fn test_raw_signed_get_balance() {
let client = build_client();
let resp = client
.raw_signed_get("/fapi/v3/balance", &[])
.await
.expect("raw_signed_get balance failed");
assert!(resp.data.is_array(), "expected array from /balance, got: {:?}", resp.data);
}
#[tokio::test]
#[ignore]
async fn test_raw_signed_delete_invalid_listen_key() {
let client = build_client();
let result = client
.raw_signed_delete("/fapi/v3/listenKey", &[("listenKey", "nonexistent")])
.await;
assert!(
matches!(result, Err(AsterDexError::ApiError { .. })) || result.is_ok(),
"expected ApiError or Ok, got: {result:?}"
);
}