use asterdex_sdk::{
CancelOrderParams, ModifyOrderParams, PlaceOrderParams, FuturesClient,
};
fn build_client() -> FuturesClient {
let _ = dotenvy::dotenv();
FuturesClient::from_env().expect("FuturesClient::from_env() failed — check .env credentials")
}
#[tokio::test]
#[ignore]
async fn test_ping_testnet() {
let _ = dotenvy::dotenv();
let client = FuturesClient::new_public("https://fapi.asterdex-testnet.com")
.expect("failed to build public client");
let resp = client.raw_get("/fapi/v3/ping", &[]).await;
assert!(resp.is_ok(), "ping failed: {resp:?}");
}
#[tokio::test]
#[ignore]
async fn test_server_time_within_tolerance() {
let _ = dotenvy::dotenv();
let client = FuturesClient::new_public("https://fapi.asterdex-testnet.com")
.expect("failed to build public client");
let resp = client.get_server_time().await.expect("get_server_time failed");
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as i64;
let diff = (resp.data.server_time as i64 - now_ms).abs();
assert!(diff < 30_000, "server time offset too large: {diff} ms");
}
#[tokio::test]
#[ignore]
async fn test_place_limit_order_btcusdt() {
let client = build_client();
let params = 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()
};
let resp = client.place_order(params).await;
assert!(resp.is_ok(), "place_order failed: {resp:?}");
let order = resp.unwrap();
assert_ne!(order.data.order_id, 0, "order_id should not be 0");
assert_eq!(order.data.symbol, "BTCUSDT");
assert_eq!(order.data.side, "BUY");
assert_eq!(order.data.status, "NEW", "expected NEW status");
let _ = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order.data.order_id),
..Default::default()
})
.await;
}
#[tokio::test]
#[ignore]
async fn test_modify_order_btcusdt() {
let client = build_client();
let place_resp = 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_order failed");
let order_id = place_resp.data.order_id;
assert_ne!(order_id, 0, "place_order returned order_id 0");
let modify_resp = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some("70500.0".to_string()),
order_id: Some(order_id),
..Default::default()
})
.await;
assert!(
modify_resp.is_ok(),
"modify_order failed: {modify_resp:?}"
);
let modified = modify_resp.unwrap();
assert_ne!(modified.data.order_id, 0);
assert_eq!(modified.data.symbol, "BTCUSDT");
assert_eq!(modified.data.status, "NEW");
assert!(
modified.data.price.starts_with("70500"),
"price was not updated — got: {}",
modified.data.price
);
let cancel_resp = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(modified.data.order_id),
..Default::default()
})
.await;
assert!(cancel_resp.is_ok(), "cancel after modify failed: {cancel_resp:?}");
assert_eq!(cancel_resp.unwrap().data.status, "CANCELED");
}
#[tokio::test]
#[ignore]
async fn test_cancel_order_btcusdt() {
let client = build_client();
let place_resp = 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_order failed");
let order_id = place_resp.data.order_id;
let cancel_resp = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order_id),
..Default::default()
})
.await;
assert!(cancel_resp.is_ok(), "cancel_order failed: {cancel_resp:?}");
let canceled = cancel_resp.unwrap();
assert_eq!(canceled.data.order_id, order_id);
assert_eq!(canceled.data.status, "CANCELED");
}
#[tokio::test]
#[ignore]
async fn test_cancel_nonexistent_order_returns_api_error() {
use asterdex_sdk::AsterDexError;
let client = build_client();
let result = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(999_999_999_999),
..Default::default()
})
.await;
assert!(
matches!(result, Err(AsterDexError::ApiError { code: -2011, .. })),
"expected ApiError -2011, got: {result:?}"
);
}
#[tokio::test]
#[ignore]
async fn test_full_order_lifecycle_btcusdt() {
let client = build_client();
let order = 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_order failed");
let order_id = order.data.order_id;
assert_ne!(order_id, 0);
assert_eq!(order.data.status, "NEW");
let open = client
.get_open_orders(Some("BTCUSDT"))
.await
.expect("get_open_orders failed");
assert!(
open.data.iter().any(|o| o.order_id == order_id),
"newly placed order not found in open orders"
);
let modified = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some("10200.0".to_string()),
order_id: Some(order_id),
..Default::default()
})
.await
.expect("modify_order failed");
let replacement_id = modified.data.order_id;
assert_ne!(replacement_id, 0);
assert_eq!(modified.data.status, "NEW");
let canceled = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(replacement_id),
..Default::default()
})
.await
.expect("cancel_order failed");
assert_eq!(canceled.data.status, "CANCELED");
let open_after = client
.get_open_orders(Some("BTCUSDT"))
.await
.expect("get_open_orders after cancel failed");
assert!(
!open_after.data.iter().any(|o| o.order_id == replacement_id),
"canceled replacement order still visible in open orders"
);
}